diff --git a/CHANGELOG.md b/CHANGELOG.md index a3cbddb..9ef58ab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Version 1.1.5](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.5) - Feature release - 2024-10-15 + +- Restrict site path, root directory override and write mode on presets + ## [Version 1.1.4](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.4) - Feature release - 2024-07-16 - Fix writing when using presets with no root folder defined diff --git a/parameter-sets/app-certificate/parameter-set.json b/parameter-sets/app-certificate/parameter-set.json index 0c9ba87..86b7098 100644 --- a/parameter-sets/app-certificate/parameter-set.json +++ b/parameter-sets/app-certificate/parameter-set.json @@ -22,6 +22,13 @@ "description": "sites/site_name/subsite...", "mandatory": true }, + { + "name": "cannot_overwrite_site", + "label": " ", + "type": "BOOLEAN", + "description": "Site path cannot be overwritten", + "mandatory": true + }, { "name": "sharepoint_root", "label": "Root directory", @@ -29,6 +36,13 @@ "description": "", "defaultValue": "Shared Documents" }, + { + "name": "cannot_overwrite_root", + "label": " ", + "type": "BOOLEAN", + "description": "Root directory cannot be overwritten", + "mandatory": true + }, { "name": "tenant_id", "label": "Tenant ID", @@ -63,6 +77,13 @@ "type": "PASSWORD", "description": "If required by private key", "mandatory": false + }, + { + "name": "cannot_write", + "label": "Read only", + "type": "BOOLEAN", + "description": "This preset is read only", + "mandatory": true } ] } \ No newline at end of file diff --git a/parameter-sets/oauth-login/parameter-set.json b/parameter-sets/oauth-login/parameter-set.json index 6471dde..3e0a4c7 100644 --- a/parameter-sets/oauth-login/parameter-set.json +++ b/parameter-sets/oauth-login/parameter-set.json @@ -34,6 +34,13 @@ "description": "sites/site_name/subsite...", "mandatory": true }, + { + "name": "cannot_overwrite_site", + "label": " ", + "type": "BOOLEAN", + "description": "Site path cannot be overwritten", + "mandatory": true + }, { "name": "sharepoint_root", "label": "Root directory", @@ -41,12 +48,26 @@ "description": "", "defaultValue": "Shared Documents" }, + { + "name": "cannot_overwrite_root", + "label": " ", + "type": "BOOLEAN", + "description": "Root directory cannot be overwritten", + "mandatory": true + }, { "name": "authorizationEndpoint", "label": "Authorization endpoint", "type": "STRING", "description": "See documentation", "mandatory": true + }, + { + "name": "cannot_write", + "label": "Read only", + "type": "BOOLEAN", + "description": "This preset is read only", + "mandatory": true } ] } \ No newline at end of file diff --git a/parameter-sets/sharepoint-login/parameter-set.json b/parameter-sets/sharepoint-login/parameter-set.json index e04ceaa..52d15df 100644 --- a/parameter-sets/sharepoint-login/parameter-set.json +++ b/parameter-sets/sharepoint-login/parameter-set.json @@ -22,6 +22,13 @@ "description": "sites/site_name/subsite...", "mandatory": true }, + { + "name": "cannot_overwrite_site", + "label": " ", + "type": "BOOLEAN", + "description": "Site path cannot be overwritten", + "mandatory": true + }, { "name": "sharepoint_root", "label": "Root directory", @@ -29,6 +36,13 @@ "description": "", "defaultValue": "Shared Documents" }, + { + "name": "cannot_overwrite_root", + "label": " ", + "type": "BOOLEAN", + "description": "Root directory cannot be overwritten", + "mandatory": true + }, { "name": "sharepoint_username", "label": "Username", @@ -42,6 +56,13 @@ "type": "PASSWORD", "description": "", "mandatory": true + }, + { + "name": "cannot_write", + "label": "Read only", + "type": "BOOLEAN", + "description": "This preset is read only", + "mandatory": true } ] } \ No newline at end of file diff --git a/parameter-sets/site-app-permissions/parameter-set.json b/parameter-sets/site-app-permissions/parameter-set.json index 0969c57..06466ec 100644 --- a/parameter-sets/site-app-permissions/parameter-set.json +++ b/parameter-sets/site-app-permissions/parameter-set.json @@ -22,6 +22,13 @@ "description": "sites/site_name/subsite...", "mandatory": true }, + { + "name": "cannot_overwrite_site", + "label": " ", + "type": "BOOLEAN", + "description": "Site path cannot be overwritten", + "mandatory": true + }, { "name": "sharepoint_root", "label": "Root directory", @@ -29,6 +36,13 @@ "description": "", "defaultValue": "Shared Documents" }, + { + "name": "cannot_overwrite_root", + "label": " ", + "type": "BOOLEAN", + "description": "Root directory cannot be overwritten", + "mandatory": true + }, { "name": "tenant_id", "label": "Tenant ID", @@ -49,6 +63,13 @@ "type": "PASSWORD", "description": "", "mandatory": true + }, + { + "name": "cannot_write", + "label": "Read only", + "type": "BOOLEAN", + "description": "This preset is read only", + "mandatory": true } ] } \ No newline at end of file diff --git a/plugin.json b/plugin.json index c357c1b..cdb516f 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "id": "sharepoint-online", - "version": "1.1.4", + "version": "1.1.5", "meta": { "label": "SharePoint Online", "description": "Read and write data from/to your SharePoint Online account", diff --git a/python-lib/dss_constants.py b/python-lib/dss_constants.py index 89fedcc..f6254da 100644 --- a/python-lib/dss_constants.py +++ b/python-lib/dss_constants.py @@ -37,7 +37,7 @@ class DSSConstants(object): "sharepoint_oauth": "The access token is missing" } PATH = 'path' - PLUGIN_VERSION = "1.1.4" + PLUGIN_VERSION = "1.1.5" SECRET_PARAMETERS_KEYS = ["Authorization", "sharepoint_username", "sharepoint_password", "client_secret", "client_certificate", "passphrase"] SITE_APP_DETAILS = { "sharepoint_tenant": "The tenant name is missing", diff --git a/python-lib/sharepoint_client.py b/python-lib/sharepoint_client.py index 656b3e0..029f54b 100644 --- a/python-lib/sharepoint_client.py +++ b/python-lib/sharepoint_client.py @@ -162,8 +162,10 @@ def apply_paths_overwrite(self, config): sharepoint_root_overwrite = config.get("sharepoint_root_overwrite", "").strip("/") sharepoint_site_overwrite = config.get("sharepoint_site_overwrite", "").strip("/") if advanced_parameters and sharepoint_root_overwrite: + assert_can_overwrite_root(config) self.sharepoint_root = sharepoint_root_overwrite if advanced_parameters and sharepoint_site_overwrite: + assert_can_overwrite_site(config) self.sharepoint_site = sharepoint_site_overwrite def setup_sharepoint_online_url(self, login_details): @@ -221,6 +223,7 @@ def get_file_content(self, full_path): return response def write_file_content(self, full_path, data): + self.assert_can_write() self.file_size = len(data) # Preventive file check out, in case it already exists on SP's side @@ -281,6 +284,7 @@ def write_chunked_file_content(self, full_path, data): return response def create_folder(self, full_path): + self.assert_can_write() if is_empty_path(full_path) and is_empty_path(self.sharepoint_root): return response = self.session.post( @@ -307,7 +311,8 @@ def create_path(self, file_full_path): if previous_status == 403 and status_code == 404: logger.error("Could not create folder for '{}'. Check your write permission for the folder {}.".format(path, previous_path)) - def move_file(self, full_from_path, full_to_path): + def move_file(self, full_from_path, full_to_path): + self.assert_can_write() get_move_url = self.get_move_url( full_from_path, full_to_path @@ -317,23 +322,27 @@ def move_file(self, full_from_path, full_to_path): return response.json() def check_in_file(self, full_path): + self.assert_can_write() logger.info("Checking in {}.".format(full_path)) file_check_in_url = self.get_file_check_in_url(full_path) self.session.post(file_check_in_url) return def check_out_file(self, full_path): + self.assert_can_write() logger.info("Checking out {}.".format(full_path)) file_check_out_url = self.get_file_check_out_url(full_path) self.session.post(file_check_out_url) return def recycle_file(self, full_path): + self.assert_can_write() recycle_file_url = self.get_recycle_file_url(full_path) response = self.session.post(recycle_file_url) self.assert_response_ok(response, calling_method="recycle_file") def recycle_folder(self, full_path): + self.assert_can_write() recycle_folder_url = self.get_recycle_folder_url(full_path) response = self.session.post(recycle_folder_url) self.assert_response_ok(response, calling_method="recycle_folder") @@ -380,6 +389,7 @@ def get_list_items(self, list_title, params=None): return response.json().get("ListData", {}) def create_list(self, list_name): + self.assert_can_write() headers = DSSConstants.JSON_HEADERS data = { '__metadata': { @@ -400,6 +410,7 @@ def create_list(self, list_name): return json.get(SharePointConstants.RESULTS_CONTAINER_V2, {}) def recycle_list(self, list_name): + self.assert_can_write() headers = DSSConstants.JSON_HEADERS response = self.session.post( self.get_lists_by_title_url(list_name)+"/recycle()", @@ -428,6 +439,7 @@ def get_web_name(self, created_list): return get_value_from_path(json_response, [SharePointConstants.RESULTS_CONTAINER_V2, "Name"]) def create_custom_field_via_id(self, list_id, field_title, field_type=None): + self.assert_can_write() field_type = SharePointConstants.FALLBACK_TYPE if field_type is None else field_type schema_xml = self.get_schema_xml(field_title, field_type) body = { @@ -458,6 +470,7 @@ def get_list_default_view(self, list_name): return json_response.get(SharePointConstants.RESULTS_CONTAINER_V2, {"Items": {"results": []}}).get("Items", {"results": []}).get("results", []) def add_column_to_list_default_view(self, column_name, list_name): + self.assert_can_write() escaped_column_name = self.escape_path(column_name) list_default_view_url = os.path.join( self.get_list_default_view_url(list_name), @@ -919,6 +932,7 @@ def escape_path(path): def get_writer(self, dataset_schema, dataset_partitioning, partition_id, max_workers, batch_size, write_mode): + self.assert_can_write() return SharePointListWriter( self.config, self, @@ -972,6 +986,12 @@ def is_column_displayable(self, column, display_metadata=False, metadata_to_retr return True return (not column[SharePointConstants.HIDDEN_COLUMN]) + def assert_can_write(self): + auth_details = get_auth_details(self.config) + cannot_write = auth_details.get("cannot_write", False) + if cannot_write: + raise SharePointClientError("This preset is read only") + class SharePointSession(): @@ -1060,6 +1080,29 @@ def get_contextinfo_url(): return form_digest_value +def assert_can_overwrite_root(config): + auth_details = get_auth_details(config) + cannot_overwrite_root = auth_details.get("cannot_overwrite_root", False) + if cannot_overwrite_root: + raise SharePointClientError("Root path overwrite is not allowed on this preset") + + +def assert_can_overwrite_site(config): + auth_details = get_auth_details(config) + cannot_overwrite_site = auth_details.get("cannot_overwrite_site", False) + if cannot_overwrite_site: + raise SharePointClientError("Site path overwrite is not allowed on this preset") + + +def get_auth_details(config): + KEY_TO_AUTH_DETAILS = {"oauth": "sharepoint_oauth", "login": "sharepoint_sharepy", "site-app-permissions": "site_app_permissions", "app-certificate": "app_certificate"} + auth_type = config.get("auth_type", None) + key_to_auth_details = KEY_TO_AUTH_DETAILS.get(auth_type, None) + if not key_to_auth_details: + return {} + return config.get(key_to_auth_details, {}) + + class SuppressFilter(logging.Filter): # Avoid poluting logs with redondant warnings # https://github.com/diyan/pywinrm/issues/269