Skip to content
Open
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
21 changes: 21 additions & 0 deletions parameter-sets/app-certificate/parameter-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,27 @@
"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",
"type": "STRING",
"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",
Expand Down Expand Up @@ -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
}
]
}
21 changes: 21 additions & 0 deletions parameter-sets/oauth-login/parameter-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,40 @@
"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",
"type": "STRING",
"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
}
]
}
21 changes: 21 additions & 0 deletions parameter-sets/sharepoint-login/parameter-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,27 @@
"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",
"type": "STRING",
"description": "",
"defaultValue": "Shared Documents"
},
{
"name": "cannot_overwrite_root",
"label": " ",
"type": "BOOLEAN",
"description": "Root directory cannot be overwritten",
"mandatory": true
},
{
"name": "sharepoint_username",
"label": "Username",
Expand All @@ -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
}
]
}
21 changes: 21 additions & 0 deletions parameter-sets/site-app-permissions/parameter-set.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,27 @@
"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",
"type": "STRING",
"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",
Expand All @@ -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
}
]
}
2 changes: 1 addition & 1 deletion plugin.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion python-lib/dss_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
45 changes: 44 additions & 1 deletion python-lib/sharepoint_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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")
Expand Down Expand Up @@ -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': {
Expand All @@ -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()",
Expand Down Expand Up @@ -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 = {
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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():

Expand Down Expand Up @@ -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
Expand Down