diff --git a/client/src/api/fileSources.ts b/client/src/api/fileSources.ts index c420b9ed3d6..fa83155d29d 100644 --- a/client/src/api/fileSources.ts +++ b/client/src/api/fileSources.ts @@ -34,7 +34,7 @@ export const templateTypes: FileSourceTypesDetail = { }, onedrive: { icon: faCloud, - message: "This is a repository plugin that connects with Microsoft OneDrive through Microsoft Graph.", + message: "This is a repository plugin that connects with Microsoft OneDrive.", }, onedata: { icon: faNetworkWired, diff --git a/doc/source/admin/data.md b/doc/source/admin/data.md index 14fe3b64a1a..d4f22558f4c 100644 --- a/doc/source/admin/data.md +++ b/doc/source/admin/data.md @@ -645,6 +645,11 @@ To use one of these templates, make the credentials available to Galaxy's web an processes using the environment variables `GALAXY_ONEDRIVE_CLIENT_ID` and `GALAXY_ONEDRIVE_CLIENT_SECRET`. Jobs themselves do not need these values and should not receive them. +If your Galaxy instance has Vault configured, you can use this Vault-backed variant instead: + +```{literalinclude} ../../../lib/galaxy/files/templates/examples/onedrive_client_secrets_in_vault.yml +:language: yaml +``` The current OneDrive implementation supports two drive modes: diff --git a/lib/galaxy/files/sources/onedrive.py b/lib/galaxy/files/sources/onedrive.py index 70a97cd8ad5..93638458fcc 100644 --- a/lib/galaxy/files/sources/onedrive.py +++ b/lib/galaxy/files/sources/onedrive.py @@ -65,11 +65,6 @@ class OneDriveFilesSource(BaseFilesSource[OneDriveFileSourceTemplateConfiguratio "Authorization": f"Bearer {config.access_token}", } - def _json_headers(self, config: OneDriveFilesSourceConfiguration) -> dict[str, str]: - headers = self._headers(config) - headers["Content-Type"] = "application/json" - return headers - def _encoded_path(self, path: str) -> str: normalized = path.strip("/") if not normalized: @@ -105,10 +100,11 @@ class OneDriveFilesSource(BaseFilesSource[OneDriveFileSourceTemplateConfiguratio method: str, url: str, context: FilesSourceRuntimeContext[OneDriveFilesSourceConfiguration], + timeout: int = 30, **kwargs, ) -> requests.Response: try: - response = requests.request(method, url, headers=self._headers(context.config), timeout=30, **kwargs) + response = requests.request(method, url, headers=self._headers(context.config), timeout=timeout, **kwargs) except requests.RequestException as exc: raise MessageException(f"Error connecting to OneDrive. Reason: {exc}") from exc @@ -181,26 +177,13 @@ class OneDriveFilesSource(BaseFilesSource[OneDriveFileSourceTemplateConfiguratio ) -> str: upload_url = self._content_url(context.config, target_path) with open(native_path, "rb") as handle: - response = requests.put( + self._request( + "PUT", upload_url, - headers={ - "Authorization": f"Bearer {context.config.access_token}", - "Content-Type": "application/octet-stream", - }, + context, data=handle, timeout=300, ) - if response.status_code in {401, 403}: - raise AuthenticationRequired( - "Permission denied while writing to OneDrive. Check the Microsoft app scopes and stored user authorization." - ) - if not response.ok: - try: - payload = response.json() - message = payload.get("error", {}).get("message", response.text) - except Exception: - message = response.text - raise MessageException(f"Error uploading to OneDrive. Reason: {message}") return self.uri_from_path(target_path) def _create_entry( @@ -215,23 +198,12 @@ class OneDriveFilesSource(BaseFilesSource[OneDriveFileSourceTemplateConfiguratio "folder": {}, "@microsoft.graph.conflictBehavior": "fail", } - response = requests.post( + response = self._request( + "POST", self._children_url(context.config, parent_path), + context, json=payload, - headers=self._json_headers(context.config), - timeout=30, ) - if response.status_code in {401, 403}: - raise AuthenticationRequired( - "Permission denied while creating a OneDrive folder. Check the Microsoft app scopes and stored user authorization." - ) - if not response.ok: - try: - body = response.json() - message = body.get("error", {}).get("message", response.text) - except Exception: - message = response.text - raise MessageException(f"Error creating OneDrive folder. Reason: {message}") item = response.json() path = self._entry_from_item(item, parent_path).path return Entry(name=item["name"], uri=self.uri_from_path(path), external_link=item.get("webUrl")) diff --git a/lib/galaxy/files/templates/examples/onedrive_client_secrets_in_vault.yml b/lib/galaxy/files/templates/examples/onedrive_client_secrets_in_vault.yml new file mode 100644 index 00000000000..9d7ba869558 --- /dev/null +++ b/lib/galaxy/files/templates/examples/onedrive_client_secrets_in_vault.yml @@ -0,0 +1,16 @@ +- id: onedrive + name: OneDrive + description: Connect to your Microsoft OneDrive app folder to download and upload files. + configuration: + type: onedrive + oauth2_client_id: "{{ environment.oauth2_client_id }}" + oauth2_client_secret: "{{ environment.oauth2_client_secret }}" + drive_mode: appfolder + writable: true + environment: + oauth2_client_id: + type: secret + vault_key: "onedrive_file_source/client_id" + oauth2_client_secret: + type: secret + vault_key: "onedrive_file_source/client_secret" diff --git a/lib/galaxy/files/templates/examples/production_onedrive.yml b/lib/galaxy/files/templates/examples/production_onedrive.yml index 61001efc613..647239363e7 100644 --- a/lib/galaxy/files/templates/examples/production_onedrive.yml +++ b/lib/galaxy/files/templates/examples/production_onedrive.yml @@ -1,6 +1,6 @@ -- id: onedrive +- id: onedrive_appfolder name: OneDrive - description: Connect to your Microsoft OneDrive app folder to download and upload files. + description: Connect to the Galaxy folder in your Microsoft OneDrive. Galaxy will only access its own app folder, not the rest of your OneDrive. configuration: type: onedrive oauth2_client_id: "{{ environment.oauth2_client_id }}" diff --git a/lib/galaxy/files/templates/examples/production_onedrive_full.yml b/lib/galaxy/files/templates/examples/production_onedrive_full.yml index b00d7838f43..df675c9ba69 100644 --- a/lib/galaxy/files/templates/examples/production_onedrive_full.yml +++ b/lib/galaxy/files/templates/examples/production_onedrive_full.yml @@ -1,6 +1,8 @@ -- id: onedrive +- id: onedrive_full name: OneDrive - description: Connect to your Microsoft OneDrive app folder to download and upload files. + description: | + Connect to your full Microsoft OneDrive. + You will be asked to grant Galaxy permission to access files across your OneDrive, not just the Galaxy app folder. configuration: type: onedrive oauth2_client_id: "{{ environment.oauth2_client_id }}" diff --git a/test/unit/files/onedrive_file_sources_conf.yml b/test/unit/files/onedrive_file_sources_conf.yml new file mode 100644 index 00000000000..0f76d776c6f --- /dev/null +++ b/test/unit/files/onedrive_file_sources_conf.yml @@ -0,0 +1,4 @@ +- type: onedrive + id: test1 + doc: Test access to a OneDrive account. + accessToken: ${user.preferences['onedrive|access_token']}