diff --git a/.env.demo b/.env.demo index 92daffd8..a4a2b2f1 100644 --- a/.env.demo +++ b/.env.demo @@ -254,6 +254,7 @@ EMAIL_SENDER=DocuElevate System # These settings are intentionally separate from the shared EMAIL_* settings above. # Configuring EMAIL_HOST for password reset / notifications does NOT automatically # enable the email destination – you must set DEST_EMAIL_HOST to activate it. +# DEST_EMAIL_ENABLED=true # Set to false to disable email delivery without removing credentials DEST_EMAIL_HOST=smtp.example.com DEST_EMAIL_PORT=587 DEST_EMAIL_USERNAME=docuelevate@example.com @@ -340,6 +341,7 @@ IMAP_READONLY_MODE=false # **Storage/Document Services** # Amazon S3 +# S3_ENABLED=true # Set to false to disable S3 uploads without removing credentials AWS_REGION=us-east-1 AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY @@ -349,12 +351,14 @@ S3_STORAGE_CLASS=STANDARD S3_ACL=private # NextCloud +# NEXTCLOUD_ENABLED=true # Set to false to disable NextCloud uploads without removing credentials NEXTCLOUD_UPLOAD_URL=https://nextcloud.example.com/remote.php/dav/files/ NEXTCLOUD_FOLDER="" NEXTCLOUD_USERNAME= NEXTCLOUD_PASSWORD= # Paperless-ngx +# PAPERLESS_ENABLED=true # Set to false to disable Paperless uploads without removing credentials PAPERLESS_HOST=https://paperless.example.com PAPERLESS_NGX_API_TOKEN= # Optional: Name of the custom field in Paperless-ngx to store the "absender" (sender) value @@ -371,12 +375,14 @@ PAPERLESS_NGX_API_TOKEN= # PAPERLESS_CUSTOM_FIELDS_MAPPING= # Dropbox +# DROPBOX_ENABLED=true # Set to false to disable Dropbox uploads without removing credentials DROPBOX_APP_KEY= DROPBOX_APP_SECRET= DROPBOX_REFRESH_TOKEN= DROPBOX_FOLDER="/Documents/Uploads" # Google Drive +# GOOGLE_DRIVE_ENABLED=true # Set to false to disable Google Drive uploads without removing credentials # Service Account Method: GOOGLE_DRIVE_CREDENTIALS_JSON={"type":"service_account","project_id":"your-project","private_key_id":"key-id","private_key":"-----BEGIN PRIVATE KEY-----\nYOUR_PRIVATE_KEY\n-----END PRIVATE KEY-----\n","client_email":"service-account@project.iam.gserviceaccount.com","client_id":"client-id","auth_uri":"https://accounts.google.com/o/oauth2/auth","token_uri":"https://oauth2.googleapis.com/token","auth_provider_x509_cert_url":"https://www.googleapis.com/oauth2/v1/certs","client_x509_cert_url":"https://www.googleapis.com/robot/v1/metadata/x509/service-account%40project.iam.gserviceaccount.com"} GOOGLE_DRIVE_FOLDER_ID= @@ -389,6 +395,7 @@ GOOGLE_DRIVE_CLIENT_SECRET=your-oauth-client-secret # Required for OAuth method GOOGLE_DRIVE_REFRESH_TOKEN=your-oauth-refresh-token # Required for OAuth method # OneDrive +# ONEDRIVE_ENABLED=true # Set to false to disable OneDrive uploads without removing credentials ONEDRIVE_CLIENT_ID=your-client-id ONEDRIVE_CLIENT_SECRET=your-client-secret ONEDRIVE_TENANT_ID=common @@ -396,6 +403,7 @@ ONEDRIVE_REFRESH_TOKEN=your-refresh-token ONEDRIVE_FOLDER_PATH=Documents/Uploads # WebDAV +# WEBDAV_ENABLED=true # Set to false to disable WebDAV uploads without removing credentials WEBDAV_URL=https://webdav.example.com/path WEBDAV_USERNAME=webdav_user WEBDAV_PASSWORD=your_secure_webdav_password @@ -403,6 +411,7 @@ WEBDAV_FOLDER=/Documents/Uploads WEBDAV_VERIFY_SSL=True # FTP +# FTP_ENABLED=true # Set to false to disable FTP uploads without removing credentials # Security Note: FTP_USE_TLS=True is strongly recommended for secure connections # Set FTP_ALLOW_PLAINTEXT=False in production to prevent unencrypted FTP FTP_HOST=ftp.example.com @@ -414,6 +423,7 @@ FTP_USE_TLS=True FTP_ALLOW_PLAINTEXT=True # SFTP +# SFTP_ENABLED=true # Set to false to disable SFTP uploads without removing credentials # Security Note: Host key verification is enabled by default (False) # Only set to True in development/testing environments if needed # When false, configure SSH known_hosts for proper host key verification @@ -427,6 +437,7 @@ SFTP_FOLDER=/Documents/Uploads SFTP_DISABLE_HOST_KEY_VERIFICATION=False # Default is False (secure); set to True only for testing # iCloud Drive +# ICLOUD_ENABLED=true # Set to false to disable iCloud uploads without removing credentials # Requires an Apple ID with iCloud Drive enabled. # For accounts with two-factor authentication (most accounts), generate an # app-specific password at https://appleid.apple.com/account/manage diff --git a/app/config.py b/app/config.py index d82143a1..1275dbbc 100644 --- a/app/config.py +++ b/app/config.py @@ -49,18 +49,30 @@ class Settings(BaseSettings): debug: bool = False # Default to False # Making Dropbox optional + dropbox_enabled: bool = Field( + default=True, + description="Enable Dropbox as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) dropbox_app_key: Optional[str] = None dropbox_app_secret: Optional[str] = None dropbox_folder: Optional[str] = None dropbox_refresh_token: Optional[str] = None # Making Nextcloud optional + nextcloud_enabled: bool = Field( + default=True, + description="Enable Nextcloud as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) nextcloud_upload_url: Optional[str] = None nextcloud_username: Optional[str] = None nextcloud_password: Optional[str] = None nextcloud_folder: Optional[str] = None # Making Paperless optional + paperless_enabled: bool = Field( + default=True, + description="Enable Paperless-ngx as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) paperless_ngx_api_token: Optional[str] = None paperless_host: Optional[str] = None paperless_custom_field_absender: Optional[str] = None # Name of the "absender" custom field in Paperless @@ -426,6 +438,10 @@ class Settings(BaseSettings): imap2_delete_after_process: bool = False # Google Drive settings + google_drive_enabled: bool = Field( + default=True, + description="Enable Google Drive as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) google_drive_credentials_json: Optional[str] = "" google_drive_folder_id: Optional[str] = "" google_drive_delegate_to: Optional[str] = "" # Optional delegated user email @@ -437,6 +453,10 @@ class Settings(BaseSettings): google_drive_refresh_token: Optional[str] = "" # WebDAV settings + webdav_enabled: bool = Field( + default=True, + description="Enable WebDAV as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) webdav_url: Optional[str] = None webdav_username: Optional[str] = None webdav_password: Optional[str] = None @@ -444,6 +464,10 @@ class Settings(BaseSettings): webdav_verify_ssl: bool = True # FTP settings + ftp_enabled: bool = Field( + default=True, + description="Enable FTP as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) ftp_host: Optional[str] = None ftp_port: Optional[int] = 21 ftp_username: Optional[str] = None @@ -453,6 +477,10 @@ class Settings(BaseSettings): ftp_allow_plaintext: bool = True # Default to allowing plaintext fallback # SFTP settings + sftp_enabled: bool = Field( + default=True, + description="Enable SFTP as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) sftp_host: Optional[str] = None sftp_port: Optional[int] = 22 sftp_username: Optional[str] = None @@ -474,6 +502,10 @@ class Settings(BaseSettings): email_default_recipient: Optional[str] = None # Email destination settings (dedicated SMTP for document delivery – decoupled from shared email above) + dest_email_enabled: bool = Field( + default=True, + description="Enable Email as an upload destination. Set to False to disable document delivery via email even when credentials are configured.", + ) dest_email_host: Optional[str] = None dest_email_port: Optional[int] = 587 dest_email_username: Optional[str] = None @@ -483,6 +515,10 @@ class Settings(BaseSettings): dest_email_default_recipient: Optional[str] = None # Fallback recipient for document delivery # OneDrive settings + onedrive_enabled: bool = Field( + default=True, + description="Enable OneDrive as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) onedrive_client_id: Optional[str] = None onedrive_client_secret: Optional[str] = None onedrive_tenant_id: Optional[str] = "common" # Default to "common" for personal accounts @@ -490,6 +526,10 @@ class Settings(BaseSettings): onedrive_folder_path: Optional[str] = None # AWS S3 settings + s3_enabled: bool = Field( + default=True, + description="Enable Amazon S3 as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) aws_access_key_id: Optional[str] = None aws_secret_access_key: Optional[str] = None aws_region: Optional[str] = "us-east-1" # Default region @@ -499,6 +539,10 @@ class Settings(BaseSettings): s3_acl: Optional[str] = "private" # Default ACL # iCloud Drive settings + icloud_enabled: bool = Field( + default=True, + description="Enable iCloud Drive as an upload destination. Set to False to disable uploads even when credentials are configured.", + ) icloud_username: Optional[str] = None # Apple ID email address icloud_password: Optional[str] = None # App-specific password (required for 2FA accounts) icloud_folder: Optional[str] = None # Target folder path in iCloud Drive (e.g. "Documents/Uploads") diff --git a/app/tasks/send_to_all.py b/app/tasks/send_to_all.py index e4ddd5b6..214e0225 100644 --- a/app/tasks/send_to_all.py +++ b/app/tasks/send_to_all.py @@ -26,18 +26,32 @@ logger = logging.getLogger(__name__) def _should_upload_to_dropbox(): - return bool(settings.dropbox_app_key and settings.dropbox_app_secret and settings.dropbox_refresh_token) + return bool( + getattr(settings, "dropbox_enabled", True) + and settings.dropbox_app_key + and settings.dropbox_app_secret + and settings.dropbox_refresh_token + ) def _should_upload_to_nextcloud(): - return bool(settings.nextcloud_upload_url and settings.nextcloud_username and settings.nextcloud_password) + return bool( + getattr(settings, "nextcloud_enabled", True) + and settings.nextcloud_upload_url + and settings.nextcloud_username + and settings.nextcloud_password + ) def _should_upload_to_paperless(): - return bool(settings.paperless_ngx_api_token and settings.paperless_host) + return bool( + getattr(settings, "paperless_enabled", True) and settings.paperless_ngx_api_token and settings.paperless_host + ) def _should_upload_to_google_drive(): + if not getattr(settings, "google_drive_enabled", True): + return False # Check for OAuth configuration if getattr(settings, "google_drive_use_oauth", False): return bool( @@ -52,20 +66,33 @@ def _should_upload_to_google_drive(): def _should_upload_to_webdav(): - return bool(settings.webdav_url and settings.webdav_username and settings.webdav_password) + return bool( + getattr(settings, "webdav_enabled", True) + and settings.webdav_url + and settings.webdav_username + and settings.webdav_password + ) def _should_upload_to_ftp(): - return bool(settings.ftp_host and settings.ftp_username and settings.ftp_password) + return bool( + getattr(settings, "ftp_enabled", True) and settings.ftp_host and settings.ftp_username and settings.ftp_password + ) def _should_upload_to_sftp(): - return bool(settings.sftp_host and settings.sftp_username and (settings.sftp_password or settings.sftp_private_key)) + return bool( + getattr(settings, "sftp_enabled", True) + and settings.sftp_host + and settings.sftp_username + and (settings.sftp_password or settings.sftp_private_key) + ) def _should_upload_to_email(): return bool( - settings.dest_email_host + getattr(settings, "dest_email_enabled", True) + and settings.dest_email_host and settings.dest_email_username and settings.dest_email_password and settings.dest_email_default_recipient @@ -73,22 +100,32 @@ def _should_upload_to_email(): def _should_upload_to_onedrive(): - return bool(settings.onedrive_client_id and settings.onedrive_client_secret and settings.onedrive_refresh_token) + return bool( + getattr(settings, "onedrive_enabled", True) + and settings.onedrive_client_id + and settings.onedrive_client_secret + and settings.onedrive_refresh_token + ) def _should_upload_to_s3(): - return bool(settings.s3_bucket_name and settings.aws_access_key_id and settings.aws_secret_access_key) + return bool( + getattr(settings, "s3_enabled", True) + and settings.s3_bucket_name + and settings.aws_access_key_id + and settings.aws_secret_access_key + ) def _should_upload_to_icloud(): - return bool(settings.icloud_username and settings.icloud_password) + return bool(getattr(settings, "icloud_enabled", True) and settings.icloud_username and settings.icloud_password) def get_configured_services_from_validator(): """ - Use the config validator to determine which services are configured properly. + Use the config validator to determine which services are configured and enabled. Returns a dictionary with service names as keys and boolean values indicating - whether they're properly configured. + whether they're properly configured AND explicitly enabled. """ providers = get_provider_status() @@ -109,7 +146,8 @@ def get_configured_services_from_validator(): result = {} for provider_name, internal_name in service_map.items(): if provider_name in providers: - result[internal_name] = providers[provider_name].get("configured", False) + provider = providers[provider_name] + result[internal_name] = provider.get("configured", False) and provider.get("enabled", True) return result diff --git a/app/utils/config_validator/providers.py b/app/utils/config_validator/providers.py index 9fce74b6..9a1e7069 100644 --- a/app/utils/config_validator/providers.py +++ b/app/utils/config_validator/providers.py @@ -144,7 +144,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "dropbox_app_secret", None) and getattr(settings, "dropbox_refresh_token", None) ), - "enabled": True, + "enabled": getattr(settings, "dropbox_enabled", True), "description": "Upload files to Dropbox cloud storage", "details": { "folder": getattr(settings, "dropbox_folder", "Not set"), @@ -161,7 +161,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "configured": bool( getattr(settings, "dest_email_host", None) and getattr(settings, "dest_email_default_recipient", None) ), - "enabled": True, + "enabled": getattr(settings, "dest_email_enabled", True), "description": "Send documents via email", "details": { "host": getattr(settings, "dest_email_host", "Not set"), @@ -183,7 +183,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "ftp_username", None) and getattr(settings, "ftp_password", None) ), - "enabled": True, + "enabled": getattr(settings, "ftp_enabled", True), "description": "Upload files to FTP server", "details": { "host": getattr(settings, "ftp_host", "Not set"), @@ -214,7 +214,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "name": "Google Drive", "icon": "fa-brands fa-google-drive", "configured": is_configured and bool(getattr(settings, "google_drive_folder_id", None)), - "enabled": True, + "enabled": getattr(settings, "google_drive_enabled", True), "description": "Store documents in Google Drive", "details": { "auth_type": "OAuth" if use_oauth else "Service Account", @@ -250,7 +250,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "nextcloud_username", None) and getattr(settings, "nextcloud_password", None) ), - "enabled": True, + "enabled": getattr(settings, "nextcloud_enabled", True), "description": "Store documents in NextCloud", "details": { "url": getattr(settings, "nextcloud_upload_url", "Not set"), @@ -270,7 +270,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "onedrive_client_secret", None) and getattr(settings, "onedrive_refresh_token", None) ), - "enabled": True, + "enabled": getattr(settings, "onedrive_enabled", True), "description": "Store documents in Microsoft OneDrive", "details": { "client_id": getattr(settings, "onedrive_client_id", "Not set"), @@ -288,7 +288,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "configured": bool( getattr(settings, "paperless_host", None) and getattr(settings, "paperless_ngx_api_token", None) ), - "enabled": True, + "enabled": getattr(settings, "paperless_enabled", True), "description": "Document management system for digital archives", "details": { "host": getattr(settings, "paperless_host", "Not set"), @@ -305,7 +305,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "aws_access_key_id", None) and getattr(settings, "aws_secret_access_key", None) ), - "enabled": True, + "enabled": getattr(settings, "s3_enabled", True), "description": "Store documents in S3-compatible object storage", "details": { "bucket": getattr(settings, "s3_bucket_name", "Not set"), @@ -327,7 +327,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "sftp_username", None) and (getattr(settings, "sftp_password", None) or getattr(settings, "sftp_private_key", None)) ), - "enabled": True, + "enabled": getattr(settings, "sftp_enabled", True), "description": "Upload files to SFTP server", "details": { "host": getattr(settings, "sftp_host", "Not set"), @@ -362,7 +362,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: and getattr(settings, "webdav_username", None) and getattr(settings, "webdav_password", None) ), - "enabled": True, + "enabled": getattr(settings, "webdav_enabled", True), "description": "Store documents on WebDAV servers", "details": { "url": getattr(settings, "webdav_url", "Not set"), @@ -378,7 +378,7 @@ def get_provider_status() -> dict[str, dict[str, object]]: "name": "iCloud Drive", "icon": "fa-brands fa-apple", "configured": bool(getattr(settings, "icloud_username", None) and getattr(settings, "icloud_password", None)), - "enabled": True, + "enabled": getattr(settings, "icloud_enabled", True), "description": "Store documents in Apple iCloud Drive", "details": { "username": getattr(settings, "icloud_username", "Not set"), diff --git a/app/utils/settings_service.py b/app/utils/settings_service.py index 956045b6..727e4861 100644 --- a/app/utils/settings_service.py +++ b/app/utils/settings_service.py @@ -642,6 +642,14 @@ SETTING_METADATA = { "options": ["us", "eu"], }, # Storage Providers - Dropbox + "dropbox_enabled": { + "category": "Storage Providers", + "description": "Enable Dropbox as an upload destination. When disabled, no documents will be sent to Dropbox even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "dropbox_app_key": { "category": "Storage Providers", "description": "Dropbox app key for OAuth authentication", @@ -675,6 +683,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - Nextcloud + "nextcloud_enabled": { + "category": "Storage Providers", + "description": "Enable Nextcloud as an upload destination. When disabled, no documents will be sent to Nextcloud even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "nextcloud_upload_url": { "category": "Storage Providers", "description": "Nextcloud WebDAV upload URL", @@ -708,6 +724,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - Paperless-ngx + "paperless_enabled": { + "category": "Storage Providers", + "description": "Enable Paperless-ngx as an upload destination. When disabled, no documents will be sent to Paperless-ngx even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "paperless_ngx_api_token": { "category": "Storage Providers", "description": "Paperless-ngx API authentication token", @@ -725,6 +749,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - Google Drive + "google_drive_enabled": { + "category": "Storage Providers", + "description": "Enable Google Drive as an upload destination. When disabled, no documents will be sent to Google Drive even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "google_drive_credentials_json": { "category": "Storage Providers", "description": "Google Drive service account credentials JSON", @@ -782,6 +814,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - OneDrive + "onedrive_enabled": { + "category": "Storage Providers", + "description": "Enable OneDrive as an upload destination. When disabled, no documents will be sent to OneDrive even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "onedrive_client_id": { "category": "Storage Providers", "description": "OneDrive OAuth client ID", @@ -823,6 +863,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - WebDAV + "webdav_enabled": { + "category": "Storage Providers", + "description": "Enable WebDAV as an upload destination. When disabled, no documents will be sent to WebDAV even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "webdav_url": { "category": "Storage Providers", "description": "WebDAV server URL", @@ -864,6 +912,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - FTP + "ftp_enabled": { + "category": "Storage Providers", + "description": "Enable FTP as an upload destination. When disabled, no documents will be sent to FTP even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "ftp_host": { "category": "Storage Providers", "description": "FTP server hostname or IP address", @@ -921,6 +977,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - SFTP + "sftp_enabled": { + "category": "Storage Providers", + "description": "Enable SFTP as an upload destination. When disabled, no documents will be sent to SFTP even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "sftp_host": { "category": "Storage Providers", "description": "SFTP server hostname or IP address", @@ -986,6 +1050,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - iCloud Drive + "icloud_enabled": { + "category": "Storage Providers", + "description": "Enable iCloud Drive as an upload destination. When disabled, no documents will be sent to iCloud Drive even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "icloud_username": { "category": "Storage Providers", "description": "Apple ID email address for iCloud Drive authentication", @@ -1019,6 +1091,14 @@ SETTING_METADATA = { "restart_required": False, }, # Storage Providers - AWS S3 + "s3_enabled": { + "category": "Storage Providers", + "description": "Enable Amazon S3 as an upload destination. When disabled, no documents will be sent to S3 even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "aws_access_key_id": { "category": "Storage Providers", "description": "AWS access key ID for S3", @@ -1152,6 +1232,14 @@ SETTING_METADATA = { "restart_required": False, }, # Email Destination Settings (dedicated SMTP for document delivery) + "dest_email_enabled": { + "category": "Email Destination", + "description": "Enable Email as an upload destination. When disabled, no documents will be delivered via email even if credentials are configured.", + "type": "boolean", + "sensitive": False, + "required": False, + "restart_required": False, + }, "dest_email_host": { "category": "Email Destination", "description": "SMTP server hostname for document delivery (separate from shared email settings)", diff --git a/docs/ConfigurationGuide.md b/docs/ConfigurationGuide.md index 4026f299..6d23b197 100644 --- a/docs/ConfigurationGuide.md +++ b/docs/ConfigurationGuide.md @@ -965,6 +965,7 @@ TESSERACT_LANGUAGE=eng+deu | **Variable** | **Description** | |-------------------------------------|-----------------------------------------------------------------------------------------------------| +| `PAPERLESS_ENABLED` | Set to `false` to disable Paperless-ngx uploads without removing credentials. Default: `true` | | `PAPERLESS_NGX_API_TOKEN` | API token for Paperless NGX. | | `PAPERLESS_HOST` | Root URL for Paperless NGX (e.g. `https://paperless.example.com`). | | `PAPERLESS_CUSTOM_FIELD_ABSENDER` | (Optional, Legacy) Name of the custom field in Paperless-ngx to store the sender ("absender") information. If set, the extracted sender will be automatically set as a custom field after document upload. Example: `Absender` or `Sender` | @@ -1007,6 +1008,7 @@ PAPERLESS_CUSTOM_FIELDS_MAPPING='{"absender": "Sender", "empfaenger": "Recipient | **Variable** | **Description** | |-------------------------|--------------------------------------------------| +| `DROPBOX_ENABLED` | Set to `false` to disable Dropbox uploads without removing credentials. Default: `true` | | `DROPBOX_APP_KEY` | Dropbox API app key. | | `DROPBOX_APP_SECRET` | Dropbox API app secret. | | `DROPBOX_REFRESH_TOKEN` | OAuth2 refresh token for Dropbox. | @@ -1018,6 +1020,7 @@ For detailed setup instructions, see the [Dropbox Setup Guide](DropboxSetup.md). | **Variable** | **Description** | |-------------------------|---------------------------------------------------------------| +| `NEXTCLOUD_ENABLED` | Set to `false` to disable Nextcloud uploads without removing credentials. Default: `true` | | `NEXTCLOUD_UPLOAD_URL` | Nextcloud WebDAV URL (e.g. `https://nc.example.com/remote.php/dav/files/`). | | `NEXTCLOUD_USERNAME` | Nextcloud login username. | | `NEXTCLOUD_PASSWORD` | Nextcloud login password. | @@ -1027,6 +1030,7 @@ For detailed setup instructions, see the [Dropbox Setup Guide](DropboxSetup.md). | **Variable** | **Description** | |---------------------------------|-------------------------------------------------------| +| `GOOGLE_DRIVE_ENABLED` | Set to `false` to disable Google Drive uploads without removing credentials. Default: `true` | | `GOOGLE_DRIVE_USE_OAUTH` | Set to `true` to use OAuth flow (recommended) | | `GOOGLE_DRIVE_CLIENT_ID` | OAuth Client ID (required if using OAuth flow) | | `GOOGLE_DRIVE_CLIENT_SECRET` | OAuth Client Secret (required if using OAuth flow) | @@ -1043,6 +1047,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS | **Variable** | **Description** | |-------------------------|---------------------------------------------------------------| +| `WEBDAV_ENABLED` | Set to `false` to disable WebDAV uploads without removing credentials. Default: `true` | | `WEBDAV_URL` | WebDAV server URL (e.g. `https://webdav.example.com/path`). | | `WEBDAV_USERNAME` | WebDAV authentication username. | | `WEBDAV_PASSWORD` | WebDAV authentication password. | @@ -1053,6 +1058,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS | **Variable** | **Description** | |-------------------------|---------------------------------------------------------------| +| `FTP_ENABLED` | Set to `false` to disable FTP uploads without removing credentials. Default: `true` | | `FTP_HOST` | FTP server hostname or IP address. | | `FTP_PORT` | FTP port (default: `21`). | | `FTP_USERNAME` | FTP authentication username. | @@ -1065,6 +1071,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS | **Variable** | **Description** | |------------------------------|-------------------------------------------------------| +| `SFTP_ENABLED` | Set to `false` to disable SFTP uploads without removing credentials. Default: `true` | | `SFTP_HOST` | SFTP server hostname or IP address. | | `SFTP_PORT` | SFTP port (default: `22`). | | `SFTP_USERNAME` | SFTP authentication username. | @@ -1096,6 +1103,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS | **Variable** | **Description** | |----------------------------------|---------------------------------------------------------------------| +| `DEST_EMAIL_ENABLED` | Set to `false` to disable email delivery without removing credentials. Default: `true` | | `DEST_EMAIL_HOST` | SMTP server hostname for document delivery. | | `DEST_EMAIL_PORT` | SMTP port for document delivery (default: `587`). | | `DEST_EMAIL_USERNAME` | SMTP authentication username for document delivery. | @@ -1108,6 +1116,7 @@ For detailed setup instructions, see the [Google Drive Setup Guide](GoogleDriveS | **Variable** | **Description** | |---------------------------------|-------------------------------------------------------| +| `ONEDRIVE_ENABLED` | Set to `false` to disable OneDrive uploads without removing credentials. Default: `true` | | `ONEDRIVE_CLIENT_ID` | Azure AD application client ID | | `ONEDRIVE_CLIENT_SECRET` | Azure AD application client secret | | `ONEDRIVE_TENANT_ID` | Azure AD tenant ID: use "common" for personal accounts or your tenant ID for corporate accounts | @@ -1120,6 +1129,7 @@ For detailed setup instructions, see the [OneDrive Setup Guide](OneDriveSetup.md | **Variable** | **Description** | |---------------------------------|-------------------------------------------------------| +| `S3_ENABLED` | Set to `false` to disable S3 uploads without removing credentials. Default: `true` | | `AWS_ACCESS_KEY_ID` | AWS IAM access key ID | | `AWS_SECRET_ACCESS_KEY` | AWS IAM secret access key | | `AWS_REGION` | AWS region where your S3 bucket is located (default: `us-east-1`) | @@ -1134,6 +1144,7 @@ For detailed setup instructions, see the [Amazon S3 Setup Guide](AmazonS3Setup.m | **Variable** | **Description** | |---------------------------------|-------------------------------------------------------| +| `ICLOUD_ENABLED` | Set to `false` to disable iCloud uploads without removing credentials. Default: `true` | | `ICLOUD_USERNAME` | Apple ID email address | | `ICLOUD_PASSWORD` | App-specific password (generate at [appleid.apple.com](https://appleid.apple.com/account/manage)) | | `ICLOUD_FOLDER` | Target folder path in iCloud Drive (e.g. `Documents/Uploads`) | diff --git a/tests/test_send_to_all.py b/tests/test_send_to_all.py index 2faad8fa..0bab975f 100644 --- a/tests/test_send_to_all.py +++ b/tests/test_send_to_all.py @@ -163,6 +163,121 @@ class TestShouldUploadFunctions: assert _should_upload_to_icloud() is False +@pytest.mark.unit +class TestShouldUploadEnabledFlag: + """Test that the _should_upload_to_* functions respect the explicit enabled flag.""" + + @patch("app.tasks.send_to_all.settings") + def test_dropbox_disabled_with_credentials(self, mock_settings): + """Test Dropbox upload is blocked when disabled even with valid credentials.""" + mock_settings.dropbox_enabled = False + mock_settings.dropbox_app_key = "key" + mock_settings.dropbox_app_secret = "secret" + mock_settings.dropbox_refresh_token = "token" + + assert _should_upload_to_dropbox() is False + + @patch("app.tasks.send_to_all.settings") + def test_nextcloud_disabled_with_credentials(self, mock_settings): + """Test Nextcloud upload is blocked when disabled even with valid credentials.""" + mock_settings.nextcloud_enabled = False + mock_settings.nextcloud_upload_url = "https://nextcloud.example.com" + mock_settings.nextcloud_username = "user" + mock_settings.nextcloud_password = "pass" + + assert _should_upload_to_nextcloud() is False + + @patch("app.tasks.send_to_all.settings") + def test_paperless_disabled_with_credentials(self, mock_settings): + """Test Paperless upload is blocked when disabled even with valid credentials.""" + mock_settings.paperless_enabled = False + mock_settings.paperless_ngx_api_token = "token" + mock_settings.paperless_host = "https://paperless.example.com" + + assert _should_upload_to_paperless() is False + + @patch("app.tasks.send_to_all.settings") + def test_google_drive_disabled_with_credentials(self, mock_settings): + """Test Google Drive upload is blocked when disabled even with valid credentials.""" + mock_settings.google_drive_enabled = False + mock_settings.google_drive_use_oauth = False + mock_settings.google_drive_credentials_json = '{"type": "service_account"}' + mock_settings.google_drive_folder_id = "folder_id" + + assert _should_upload_to_google_drive() is False + + @patch("app.tasks.send_to_all.settings") + def test_webdav_disabled_with_credentials(self, mock_settings): + """Test WebDAV upload is blocked when disabled even with valid credentials.""" + mock_settings.webdav_enabled = False + mock_settings.webdav_url = "https://webdav.example.com" + mock_settings.webdav_username = "user" + mock_settings.webdav_password = "pass" + + assert _should_upload_to_webdav() is False + + @patch("app.tasks.send_to_all.settings") + def test_ftp_disabled_with_credentials(self, mock_settings): + """Test FTP upload is blocked when disabled even with valid credentials.""" + mock_settings.ftp_enabled = False + mock_settings.ftp_host = "ftp.example.com" + mock_settings.ftp_username = "user" + mock_settings.ftp_password = "pass" + + assert _should_upload_to_ftp() is False + + @patch("app.tasks.send_to_all.settings") + def test_sftp_disabled_with_credentials(self, mock_settings): + """Test SFTP upload is blocked when disabled even with valid credentials.""" + mock_settings.sftp_enabled = False + mock_settings.sftp_host = "sftp.example.com" + mock_settings.sftp_username = "user" + mock_settings.sftp_password = "pass" + mock_settings.sftp_private_key = None + + assert _should_upload_to_sftp() is False + + @patch("app.tasks.send_to_all.settings") + def test_email_disabled_with_credentials(self, mock_settings): + """Test email upload is blocked when disabled even with valid credentials.""" + mock_settings.dest_email_enabled = False + mock_settings.dest_email_host = "smtp.example.com" + mock_settings.dest_email_username = "user" + mock_settings.dest_email_password = "pass" + mock_settings.dest_email_default_recipient = "recipient@example.com" + + assert _should_upload_to_email() is False + + @patch("app.tasks.send_to_all.settings") + def test_onedrive_disabled_with_credentials(self, mock_settings): + """Test OneDrive upload is blocked when disabled even with valid credentials.""" + mock_settings.onedrive_enabled = False + mock_settings.onedrive_client_id = "client_id" + mock_settings.onedrive_client_secret = "client_secret" + mock_settings.onedrive_refresh_token = "refresh_token" + + assert _should_upload_to_onedrive() is False + + @patch("app.tasks.send_to_all.settings") + def test_s3_disabled_with_credentials(self, mock_settings): + """Test S3 upload is blocked when disabled even with valid credentials.""" + mock_settings.s3_enabled = False + mock_settings.s3_bucket_name = "my-bucket" + mock_settings.aws_access_key_id = "key_id" + mock_settings.aws_secret_access_key = "secret_key" + + assert _should_upload_to_s3() is False + + @patch("app.tasks.send_to_all.settings") + def test_icloud_disabled_with_credentials(self, mock_settings): + """Test iCloud upload is blocked when disabled even with valid credentials.""" + mock_settings.icloud_enabled = False + mock_settings.icloud_username = "user@example.com" + mock_settings.icloud_password = "app-specific-password" + + assert _should_upload_to_icloud() is False + + @pytest.mark.unit class TestGetConfiguredServicesFromValidator: """Test get_configured_services_from_validator function.""" @@ -171,9 +286,9 @@ class TestGetConfiguredServicesFromValidator: def test_returns_configured_services(self, mock_get_status): """Test that configured services are returned correctly.""" mock_get_status.return_value = { - "Dropbox": {"configured": True}, - "NextCloud": {"configured": False}, - "S3 Storage": {"configured": True}, + "Dropbox": {"configured": True, "enabled": True}, + "NextCloud": {"configured": False, "enabled": True}, + "S3 Storage": {"configured": True, "enabled": True}, } result = get_configured_services_from_validator() @@ -186,7 +301,7 @@ class TestGetConfiguredServicesFromValidator: def test_handles_missing_providers(self, mock_get_status): """Test handling when some providers are not in status.""" mock_get_status.return_value = { - "Dropbox": {"configured": True}, + "Dropbox": {"configured": True, "enabled": True}, } result = get_configured_services_from_validator() @@ -194,6 +309,30 @@ class TestGetConfiguredServicesFromValidator: assert result["dropbox"] is True # Other services not in result + @patch("app.tasks.send_to_all.get_provider_status") + def test_configured_but_disabled_service_not_active(self, mock_get_status): + """Test that a configured but disabled service is not returned as active.""" + mock_get_status.return_value = { + "Dropbox": {"configured": True, "enabled": False}, + "S3 Storage": {"configured": True, "enabled": True}, + } + + result = get_configured_services_from_validator() + + assert result["dropbox"] is False + assert result["s3"] is True + + @patch("app.tasks.send_to_all.get_provider_status") + def test_missing_enabled_field_defaults_to_true_for_backward_compatibility(self, mock_get_status): + """Test that missing 'enabled' key defaults to True (backward compatible).""" + mock_get_status.return_value = { + "Dropbox": {"configured": True}, + } + + result = get_configured_services_from_validator() + + assert result["dropbox"] is True + @pytest.mark.unit class TestSendToAllDestinations: