From bcd5c7634ca22191480e5ce153eecc7c0c58e7e5 Mon Sep 17 00:00:00 2001 From: vgrem Date: Sun, 25 Feb 2024 15:10:35 +0200 Subject: [PATCH] refactorings & examples updates --- .../sharepoint/listitems/system_update.py | 27 +- .../sharepoint/taxonomy/export_term_store.py | 6 +- generator/import_metadata.py | 4 +- generator/metadata/MicrosoftGraph.xml | 1213 ++++++++++------- generator/metadata/SharePoint.xml | 1 + .../attacksimulations/landing_page.py | 5 + .../security/attacksimulations/operation.py | 5 + .../security/attacksimulations/root.py | 31 + .../onedrive/analytics/item_activity_stat.py | 9 + .../onedrive/analytics/item_analytics.py | 1 + office365/sharepoint/fields/collection.py | 15 +- office365/sharepoint/fields/field.py | 8 +- .../sharepoint/listitems/form_update_value.py | 18 +- office365/sharepoint/taxonomy/field.py | 14 + tests/data/ImportTermSet.csv | 13 + tests/directory/test_security.py | 8 + tests/onedrive/test_site.py | 11 +- 17 files changed, 849 insertions(+), 540 deletions(-) create mode 100644 office365/directory/security/attacksimulations/landing_page.py create mode 100644 office365/directory/security/attacksimulations/operation.py create mode 100644 tests/data/ImportTermSet.csv diff --git a/examples/sharepoint/listitems/system_update.py b/examples/sharepoint/listitems/system_update.py index 86f51f269..4bcd291bd 100644 --- a/examples/sharepoint/listitems/system_update.py +++ b/examples/sharepoint/listitems/system_update.py @@ -1,29 +1,40 @@ +""" +Demonstrates how to update system metadata properties of List Item +""" + import sys from datetime import datetime, timedelta from office365.sharepoint.client_context import ClientContext from office365.sharepoint.fields.user_value import FieldUserValue -from office365.sharepoint.listitems.listitem import ListItem -from tests import test_client_credentials, test_team_site_url, test_user_principal_name +from tests import ( + test_client_credentials, + test_site_url, + test_user_principal_name, +) -ctx = ClientContext(test_team_site_url).with_credentials(test_client_credentials) +ctx = ClientContext(test_site_url).with_credentials(test_client_credentials) -list_tasks = ctx.web.lists.get_by_title("Company Tasks") -items = list_tasks.items.get().top(1).execute_query() +target_list = ctx.web.lists.get_by_title("Documents") +items = target_list.items.get().top(1).execute_query() if len(items) == 0: sys.exit("No items were found") -item_to_update = items[0] # type: ListItem +item_to_update = items[0] author = ctx.web.site_users.get_by_email(test_user_principal_name) -modified_date = datetime.utcnow() - timedelta(days=3) +created_date = datetime.utcnow() - timedelta(days=14) +modified_date = datetime.utcnow() - timedelta(days=7) result = item_to_update.validate_update_list_item( { "Title": "Task (updated)", - "Author": FieldUserValue.from_user(author), + "Editor": FieldUserValue.from_user(author), "Modified": modified_date, + "Created": created_date, + "Author": FieldUserValue.from_user(author), }, dates_in_utc=True, + new_document_update=True, ).execute_query() has_any_error = any([item.HasException for item in result.value]) diff --git a/examples/sharepoint/taxonomy/export_term_store.py b/examples/sharepoint/taxonomy/export_term_store.py index a87d7d57a..9914530c6 100644 --- a/examples/sharepoint/taxonomy/export_term_store.py +++ b/examples/sharepoint/taxonomy/export_term_store.py @@ -9,7 +9,7 @@ ctx = ClientContext(test_team_site_url).with_credentials(test_client_credentials) store = ctx.taxonomy.term_store -term_groups = ctx.taxonomy.term_store.term_groups.get_all().execute_query() +term_groups = ctx.taxonomy.term_store.term_groups.get().execute_query() for term_group in term_groups: - term_sets = term_group.term_sets.get_all().execute_query() - print(json.dumps(term_sets.to_json(), indent=4)) + term_sets = term_group.term_sets.get().execute_query() +print(json.dumps(term_groups.to_json(), indent=4)) diff --git a/generator/import_metadata.py b/generator/import_metadata.py index 16166b566..cddc3963e 100644 --- a/generator/import_metadata.py +++ b/generator/import_metadata.py @@ -21,13 +21,13 @@ def export_to_file(path, content): "--endpoint", dest="endpoint", help="Import metadata endpoint", - default="sharepoint", + default="microsoftgraph", ) parser.add_argument( "-p", "--path", dest="path", - default="./metadata/SharePoint.xml", + default="./metadata/MicrosoftGraph.xml", help="Import metadata endpoint", ) diff --git a/generator/metadata/MicrosoftGraph.xml b/generator/metadata/MicrosoftGraph.xml index 17c31b7f8..2169420f1 100644 --- a/generator/metadata/MicrosoftGraph.xml +++ b/generator/metadata/MicrosoftGraph.xml @@ -4500,6 +4500,15 @@ + + + + + + + + + @@ -4608,6 +4617,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -14876,6 +14929,9 @@ + + + @@ -15590,6 +15646,12 @@ + + + + + + @@ -15638,12 +15700,6 @@ - - - - - - @@ -15773,6 +15829,12 @@ + + + + + + @@ -16168,6 +16230,11 @@ + + + + + @@ -16660,6 +16727,19 @@ + + + + + + + + + + + + + @@ -16667,6 +16747,18 @@ + + + + + + + + + + + + @@ -18549,6 +18641,11 @@ + + + + + @@ -20917,6 +21014,7 @@ + @@ -21395,6 +21493,7 @@ + @@ -21405,6 +21504,9 @@ + + + @@ -21748,6 +21850,19 @@ + + + + + + + + + + + + + @@ -22080,6 +22195,7 @@ + @@ -22102,6 +22218,15 @@ + + + + + + + + + @@ -23731,6 +23856,24 @@ + + + + + + + + + + + + + + + + + + @@ -23998,7 +24141,9 @@ + + @@ -25865,12 +26010,21 @@ + + + + + + + + + @@ -29887,6 +30041,12 @@ + + + + + + @@ -30066,6 +30226,22 @@ + + + + + + + + + + + + + + + + @@ -31638,6 +31814,7 @@ + @@ -31670,6 +31847,7 @@ + @@ -31954,6 +32132,518 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -35960,513 +36650,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -37270,6 +37453,9 @@ + + + @@ -37909,6 +38095,7 @@ + diff --git a/generator/metadata/SharePoint.xml b/generator/metadata/SharePoint.xml index e3964c9e5..8b4182885 100644 --- a/generator/metadata/SharePoint.xml +++ b/generator/metadata/SharePoint.xml @@ -39966,6 +39966,7 @@ + diff --git a/office365/directory/security/attacksimulations/landing_page.py b/office365/directory/security/attacksimulations/landing_page.py new file mode 100644 index 000000000..365d5a1c1 --- /dev/null +++ b/office365/directory/security/attacksimulations/landing_page.py @@ -0,0 +1,5 @@ +from office365.entity import Entity + + +class LandingPage(Entity): + """Represents an attack simulation landing page.""" diff --git a/office365/directory/security/attacksimulations/operation.py b/office365/directory/security/attacksimulations/operation.py new file mode 100644 index 000000000..b79ce0178 --- /dev/null +++ b/office365/directory/security/attacksimulations/operation.py @@ -0,0 +1,5 @@ +from office365.onedrive.operations.long_running import LongRunningOperation + + +class AttackSimulationOperation(LongRunningOperation): + """Represents the status of a long-running attack simulation training operation.""" diff --git a/office365/directory/security/attacksimulations/root.py b/office365/directory/security/attacksimulations/root.py index 2017dccd5..d0662be89 100644 --- a/office365/directory/security/attacksimulations/root.py +++ b/office365/directory/security/attacksimulations/root.py @@ -1,6 +1,10 @@ from office365.directory.security.attacksimulations.automation import ( SimulationAutomation, ) +from office365.directory.security.attacksimulations.landing_page import LandingPage +from office365.directory.security.attacksimulations.operation import ( + AttackSimulationOperation, +) from office365.directory.security.attacksimulations.simulation import Simulation from office365.entity import Entity from office365.entity_collection import EntityCollection @@ -11,6 +15,32 @@ class AttackSimulationRoot(Entity): """Represents an abstract type that provides the ability to launch a realistic phishing attack that organizations can learn from.""" + @property + def landing_pages(self): + # type: () -> EntityCollection[LandingPage] + """Represents an attack simulation training landing page.""" + return self.properties.get( + "landingPages", + EntityCollection( + self.context, + LandingPage, + ResourcePath("landingPages", self.resource_path), + ), + ) + + @property + def operations(self): + # type: () -> EntityCollection[AttackSimulationOperation] + """Represents an attack simulation training operation.""" + return self.properties.get( + "operations", + EntityCollection( + self.context, + AttackSimulationOperation, + ResourcePath("operations", self.resource_path), + ), + ) + @property def simulations(self): # type: () -> EntityCollection[Simulation] @@ -40,6 +70,7 @@ def simulation_automations(self): def get_property(self, name, default_value=None): if default_value is None: property_mapping = { + "landingPages": self.landing_pages, "simulationAutomations": self.simulation_automations, } default_value = property_mapping.get(name, None) diff --git a/office365/onedrive/analytics/item_activity_stat.py b/office365/onedrive/analytics/item_activity_stat.py index 093ae3ae1..9aaafddef 100644 --- a/office365/onedrive/analytics/item_activity_stat.py +++ b/office365/onedrive/analytics/item_activity_stat.py @@ -65,3 +65,12 @@ def activities(self): ResourcePath("activities", self.resource_path), ), ) + + def get_property(self, name, default_value=None): + if default_value is None: + property_mapping = { + "endDateTime": self.end_datetime, + "startDateTime": self.start_datetime, + } + default_value = property_mapping.get(name, None) + return super(ItemActivityStat, self).get_property(name, default_value) diff --git a/office365/onedrive/analytics/item_analytics.py b/office365/onedrive/analytics/item_analytics.py index c958f7af6..8b4a13d47 100644 --- a/office365/onedrive/analytics/item_analytics.py +++ b/office365/onedrive/analytics/item_analytics.py @@ -19,6 +19,7 @@ def all_time(self): @property def item_activity_stats(self): + # type: () -> EntityCollection[ItemActivityStat] return self.properties.get( "itemActivityStats", EntityCollection( diff --git a/office365/sharepoint/fields/collection.py b/office365/sharepoint/fields/collection.py index 759caec0c..39aa1eceb 100644 --- a/office365/sharepoint/fields/collection.py +++ b/office365/sharepoint/fields/collection.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import TYPE_CHECKING, Optional from office365.runtime.paths.service_operation import ServiceOperationPath from office365.runtime.queries.create_entity import CreateEntityQuery @@ -20,6 +20,10 @@ from office365.sharepoint.taxonomy.sets.set import TermSet from office365.sharepoint.taxonomy.stores.store import TermStore +if TYPE_CHECKING: + from office365.sharepoint.lists.list import List + from office365.sharepoint.webs.web import Web + class FieldCollection(EntityCollection[Field]): """Represents a collection of Field resource.""" @@ -148,8 +152,6 @@ def _add_lookup_field(lookup_list_id): return_type=return_type, ) - from office365.sharepoint.lists.list import List - if isinstance(lookup_list, List): def _lookup_list_loaded(): @@ -312,7 +314,7 @@ def get_by_id(self, _id): ) def get_by_internal_name_or_title(self, value): - """Returns the first field (2) in the collection based on the internal name or the title specified + """Returns the first field in the collection based on the internal name or the title specified by the parameter. :param str value: The title or internal name to look up the field (2) by. @@ -334,3 +336,8 @@ def get_by_title(self, title): self.context, ServiceOperationPath("getByTitle", [title], self.resource_path), ) + + @property + def parent(self): + # type: () -> Web|List + return self._parent diff --git a/office365/sharepoint/fields/field.py b/office365/sharepoint/fields/field.py index fb9bdaa73..5f9deb179 100644 --- a/office365/sharepoint/fields/field.py +++ b/office365/sharepoint/fields/field.py @@ -194,17 +194,13 @@ def group(self, val): @property def internal_name(self): # type: () -> Optional[str] - """ - Gets a value that specifies the field internal name. - """ + """Gets a value that specifies the field internal name.""" return self.properties.get("InternalName", None) @property def can_be_deleted(self): # type: () -> Optional[bool] - """ - Gets a value that specifies whether the field can be deleted - """ + """Gets a value that specifies whether the field can be deleted""" return self.properties.get("CanBeDeleted", None) @property diff --git a/office365/sharepoint/listitems/form_update_value.py b/office365/sharepoint/listitems/form_update_value.py index 1d8c26b17..d107f2d57 100644 --- a/office365/sharepoint/listitems/form_update_value.py +++ b/office365/sharepoint/listitems/form_update_value.py @@ -7,7 +7,14 @@ class ListItemFormUpdateValue(ClientValue): """Specifies the properties of a list item field and its value.""" - def __init__(self, name=None, value=None, has_exception=None, error_code=None): + def __init__( + self, + name=None, + value=None, + has_exception=None, + error_code=None, + error_message=None, + ): """ :param str name: Specifies the field internal name for a field. :param str value: Specifies a value for a field. @@ -19,6 +26,15 @@ def __init__(self, name=None, value=None, has_exception=None, error_code=None): self.FieldValue = value self.HasException = has_exception self.ErrorCode = error_code + self.ErrorMessage = error_message + + def __repr__(self): + if self.HasException: + return "{0} update failed: Message: {1}".format( + self.FieldName, self.ErrorMessage + ) + else: + return "{0} update succeeded".format(self.FieldName) def to_json(self, json_format=None): json = super(ListItemFormUpdateValue, self).to_json(json_format) diff --git a/office365/sharepoint/taxonomy/field.py b/office365/sharepoint/taxonomy/field.py index 0398588ee..4275a7466 100644 --- a/office365/sharepoint/taxonomy/field.py +++ b/office365/sharepoint/taxonomy/field.py @@ -10,6 +10,20 @@ class TaxonomyField(FieldLookup): """Represents a taxonomy field.""" + def set_field_value_by_value(self, item, tax_value): + """ + Sets the value of the corresponding field in the list item to the value of the specified TaxonomyFieldValue + :param ListItem item: The ListItem object whose field is to be updated. + :param TaxonomyFieldValue tax_value: The TaxonomyFieldValue object whose value is to be used to + update this field. + """ + + def _set_field_value_by_value(): + item.set_property(self.internal_name, tax_value) + + self.ensure_property("InternalName", _set_field_value_by_value) + return self + @staticmethod def create( fields, diff --git a/tests/data/ImportTermSet.csv b/tests/data/ImportTermSet.csv new file mode 100644 index 000000000..26728ab5f --- /dev/null +++ b/tests/data/ImportTermSet.csv @@ -0,0 +1,13 @@ +"Term Set Name","Term Set Description","LCID","Available for Tagging","Term Description","Level 1 Term","Level 2 Term","Level 3 Term","Level 4 Term","Level 5 Term","Level 6 Term","Level 7 Term" +"Political Geography","A sample term set, describing a simple political geography.",,True,"One of the seven main land masses (Europe, Asia, Africa, North America, South America, Australia, and Antarctica)","Continent",,,,,, +,,,True,"Entity defined by people, not visible to the naked eye","Continent","Political Entity",,,,, +,,,True,"Politically defined state with a geographic area governed by a central government","Continent","Political Entity","Country",,,, +,,,True,"Administrative division of a country","Continent","Political Entity","Country","Province or State",,, +,,,True,"Large sub-region usually containing many cities and towns","Continent","Political Entity","Country","Province or State","County or Region",, +,,,True,"Small village","Continent","Political Entity","Country","Province or State","County or Region","Hamlet", +,,,True,"Collection of homes and business, often incorporated","Continent","Political Entity","Country","Province or State","County or Region","Village", +,,,True,"A small city","Continent","Political Entity","Country","Province or State","County or Region","Town", +,,,True,"An incorporated town with a large population, usually governed by a mayor or council","Continent","Political Entity","Country","Province or State","County or Region","City", +,,,True,"A division of a city, often repesented in the city government","Continent","Political Entity","Country","Province or State","County or Region","City","District" +,,,True,"A sub-section of a city","Continent","Political Entity","Country","Province or State","County or Region","City","Borough" +,,,True,"Unofficial district or area of a city or town","Continent","Political Entity","Country","Province or State","County or Region","City","Neighborhood" diff --git a/tests/directory/test_security.py b/tests/directory/test_security.py index c2d1d526a..a41d58e3a 100644 --- a/tests/directory/test_security.py +++ b/tests/directory/test_security.py @@ -16,3 +16,11 @@ def test1_list_incidents(self): # def test2_list_threat_assessment_requests(self): # col = self.client.information_protection.threat_assessment_requests.top(10).get().execute_query() # self.assertIsNotNone(col.resource_path) + + # def test3_list_landing_pages(self): + # col = ( + # self.client.security.attack_simulation.landing_pages.filter("source eq 'tenant'") + # .get() + # .execute_query() + # ) + # self.assertIsNotNone(col.resource_path) diff --git a/tests/onedrive/test_site.py b/tests/onedrive/test_site.py index 9ec604e5d..c092ec233 100644 --- a/tests/onedrive/test_site.py +++ b/tests/onedrive/test_site.py @@ -40,11 +40,16 @@ def test6_unfollow(self): pass def test7_get_applicable_content_types_for_list(self): - my_site = self.client.sites.root - doc_lib = my_site.lists["Documents"].get().execute_query() - cts = my_site.get_applicable_content_types_for_list(doc_lib.id).execute_query() + site = self.client.sites.root + doc_lib = site.lists["Documents"].get().execute_query() + cts = site.get_applicable_content_types_for_list(doc_lib.id).execute_query() self.assertIsNotNone(cts.resource_path) def test8_get_operations(self): ops = self.client.sites.root.operations.get().execute_query() self.assertIsNotNone(ops.resource_path) + + def test9_get_analytics(self): + site = self.client.sites.root + result = site.analytics.last_seven_days.get().execute_query() + self.assertIsNotNone(result.resource_path)