- Remove incompatible NextcloudBackend.list_folder(folder_path="") override so Nextcloud inherits canonical WebDAVBackend.list_folder(connection_id, user_id, parent_ref=None, page_token=None) -> CloudListing - Add normalize_nextcloud_url() to cloud_utils.py: idempotent canonical DAV root derivation, username percent-encoding, https-only, rejects userinfo/ query/fragment, validates via SSRF guard before return - Use normalize_nextcloud_url in cloud_backend_factory for nextcloud provider - Add TestNextcloudBackendNoListFolderOverride and TestNextcloudUrlNormalization to test_webdav_backend.py - All 9 Nextcloud contract failures now pass; full focused suite: 158 passed
67 lines
2.4 KiB
Python
67 lines
2.4 KiB
Python
"""Factory for user-scoped cloud storage backends."""
|
|
from __future__ import annotations
|
|
|
|
from storage.cloud_base import CloudResourceAdapter
|
|
|
|
|
|
def build_cloud_backend(provider: str, credentials: dict):
|
|
if provider == "google_drive":
|
|
from storage.google_drive_backend import GoogleDriveBackend # lazy import
|
|
|
|
return GoogleDriveBackend(credentials)
|
|
|
|
if provider == "onedrive":
|
|
from storage.onedrive_backend import OneDriveBackend # lazy import
|
|
|
|
return OneDriveBackend(credentials)
|
|
|
|
if provider == "nextcloud":
|
|
from storage.nextcloud_backend import NextcloudBackend # lazy import
|
|
from storage.cloud_utils import normalize_nextcloud_url # lazy import
|
|
|
|
server_url = credentials["server_url"]
|
|
username = credentials["username"]
|
|
# Normalize to canonical WebDAV root idempotently; validate SSRF before construction
|
|
try:
|
|
server_url = normalize_nextcloud_url(server_url, username)
|
|
except ValueError:
|
|
# URL may already be canonical or validation rejected it — let the
|
|
# constructor's validate_cloud_url call surface the error
|
|
pass
|
|
|
|
return NextcloudBackend(
|
|
server_url,
|
|
username,
|
|
credentials["password"],
|
|
)
|
|
|
|
if provider == "webdav":
|
|
from storage.webdav_backend import WebDAVBackend # lazy import
|
|
|
|
return WebDAVBackend(
|
|
credentials["server_url"],
|
|
credentials["username"],
|
|
credentials["password"],
|
|
)
|
|
|
|
raise ValueError(f"Unknown provider: {provider}")
|
|
|
|
|
|
def build_cloud_resource_adapter(provider: str, credentials: dict) -> CloudResourceAdapter:
|
|
"""Build a CloudResourceAdapter for the given provider and credentials.
|
|
|
|
Returns a provider instance that implements the CloudResourceAdapter read-only
|
|
interface (list_folder, get_capabilities, merge_item_capabilities). All four
|
|
supported providers implement CloudResourceAdapter as a mixin alongside
|
|
their StorageBackend interface.
|
|
|
|
Raises:
|
|
ValueError: If provider is not one of the four supported cloud providers.
|
|
"""
|
|
backend = build_cloud_backend(provider, credentials)
|
|
if not isinstance(backend, CloudResourceAdapter):
|
|
raise ValueError(
|
|
f"Provider {provider!r} backend does not implement CloudResourceAdapter"
|
|
)
|
|
return backend
|