From 70144b835c38c0c4354c091cdd5c36eb617dcb42 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Fri, 16 Feb 2024 11:05:53 +0100 Subject: [PATCH 1/5] Add read only, lock site, lock root restrictions to presets --- parameter-sets/oauth-login/parameter-set.json | 21 +++++++++ .../sharepoint-login/parameter-set.json | 21 +++++++++ .../site-app-permissions/parameter-set.json | 21 +++++++++ python-lib/sharepoint_client.py | 44 +++++++++++++++++++ 4 files changed, 107 insertions(+) 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/python-lib/sharepoint_client.py b/python-lib/sharepoint_client.py index 3c92c50..2dc9c4f 100644 --- a/python-lib/sharepoint_client.py +++ b/python-lib/sharepoint_client.py @@ -138,8 +138,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): @@ -197,6 +199,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 @@ -257,6 +260,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( @@ -266,6 +270,7 @@ def create_folder(self, full_path): return response 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 @@ -275,23 +280,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") @@ -338,6 +347,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': { @@ -358,6 +368,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()", @@ -386,6 +397,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 = { @@ -416,6 +428,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), @@ -854,6 +867,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, @@ -907,6 +921,13 @@ 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): + print("ALX:config={}".format(self.config)) + 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(): @@ -995,6 +1016,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("The root path cannot be overwritten 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("The site cannot be overwritten on this preset") + + +def get_auth_details(config): + KEY_TO_AUTH_DETAILS = {"oauth": "sharepoint_oauth", "login": "sharepoint_sharepy", "site-app-permissions": "site_app_permissions"} + 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 From 3ef999fbd4b8ad495806e0458f22c44a711d5bd8 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Tue, 4 Jun 2024 20:05:03 +0200 Subject: [PATCH 2/5] update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27bebf4..6c57c84 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## [Version 1.1.3](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.3) - Feature release - 2024-06-04 + +- Restrict site path, root directory override and write mode on presets + ## [Version 1.1.2](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.2) - Bugfix release - 2024-05-28 - Fix path creation inside read-only parent directory From 3996ca745cce7e3ef7e1e6c164808ca1f8b13e95 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Tue, 4 Jun 2024 20:05:15 +0200 Subject: [PATCH 3/5] v1.1.2-b2 --- plugin.json | 2 +- python-lib/dss_constants.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin.json b/plugin.json index 6fa43d2..ffead09 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "id": "sharepoint-online", - "version": "1.1.2", + "version": "1.1.3", "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 1be729e..f7a3ab0 100644 --- a/python-lib/dss_constants.py +++ b/python-lib/dss_constants.py @@ -28,7 +28,7 @@ class DSSConstants(object): "sharepoint_oauth": "The access token is missing" } PATH = 'path' - PLUGIN_VERSION = "1.1.2" + PLUGIN_VERSION = "1.1.2-beta.2" SECRET_PARAMETERS_KEYS = ["Authorization", "sharepoint_username", "sharepoint_password", "client_secret"] SITE_APP_DETAILS = { "sharepoint_tenant": "The tenant name is missing", From 5d4ff0014b500855de7b9363453072f77a0cbb41 Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Wed, 5 Jun 2024 16:57:20 +0200 Subject: [PATCH 4/5] Add preset restrictions to certificate preset --- .../app-certificate/parameter-set.json | 21 +++++++++++++++++++ python-lib/sharepoint_client.py | 7 +++---- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/parameter-sets/app-certificate/parameter-set.json b/parameter-sets/app-certificate/parameter-set.json index 1584576..698cdea 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/python-lib/sharepoint_client.py b/python-lib/sharepoint_client.py index b06f7ba..13ed520 100644 --- a/python-lib/sharepoint_client.py +++ b/python-lib/sharepoint_client.py @@ -978,7 +978,6 @@ def is_column_displayable(self, column, display_metadata=False, metadata_to_retr return (not column[SharePointConstants.HIDDEN_COLUMN]) def assert_can_write(self): - print("ALX:config={}".format(self.config)) auth_details = get_auth_details(self.config) cannot_write = auth_details.get("cannot_write", False) if cannot_write: @@ -1076,18 +1075,18 @@ 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("The root path cannot be overwritten on this preset") + 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("The site cannot be overwritten on this preset") + 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"} + 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: From 2d0efb0fbaf610206b6785a7026b577ff4993f6a Mon Sep 17 00:00:00 2001 From: Alexandre Bourret Date: Mon, 10 Jun 2024 17:31:47 +0200 Subject: [PATCH 5/5] v1.1.4 --- CHANGELOG.md | 5 ++++- plugin.json | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c234cb..f55e239 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,11 @@ # Changelog -## [Version 1.1.3](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.3) - Feature release - 2024-06-04 +## [Version 1.1.4](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.4) - Feature release - 2024-06-10 - Restrict site path, root directory override and write mode on presets + +## [Version 1.1.3](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.3) - Feature release - 2024-06-04 + - Add login with Azure AD app certificate ## [Version 1.1.2](https://github.com/dataiku/dss-plugin-sharepoint-online/releases/tag/v1.1.2) - Bugfix release - 2024-05-28 diff --git a/plugin.json b/plugin.json index ffead09..c357c1b 100644 --- a/plugin.json +++ b/plugin.json @@ -1,6 +1,6 @@ { "id": "sharepoint-online", - "version": "1.1.3", + "version": "1.1.4", "meta": { "label": "SharePoint Online", "description": "Read and write data from/to your SharePoint Online account",