Add Google Drive authorization processing and error handling templates

- Implemented `google_drive_callback.html` for processing Google Drive authorization, including UI for success and error states.
- Added JavaScript functionality for exchanging authorization codes, saving settings, and handling folder selection.
- Created `google_drive_callback_error.html` to display error messages during the authorization process.
This commit is contained in:
Christian Krakau-Louis
2025-04-11 00:13:57 +02:00
parent b258dd2f29
commit 4c50c0aae6
26 changed files with 3297 additions and 727 deletions
+83 -10
View File
@@ -14,6 +14,7 @@ from app.tasks.upload_to_sftp import upload_to_sftp
from app.tasks.upload_to_email import upload_to_email
from app.tasks.upload_to_onedrive import upload_to_onedrive
from app.tasks.upload_to_s3 import upload_to_s3
from app.utils.config_validator import get_provider_status
from app.celery_app import celery
logger = logging.getLogger(__name__)
@@ -33,7 +34,16 @@ def _should_upload_to_paperless():
settings.paperless_host)
def _should_upload_to_google_drive():
return settings.google_drive_credentials_json
# Check for OAuth configuration
if getattr(settings, 'google_drive_use_oauth', False):
return (settings.google_drive_client_id and
settings.google_drive_client_secret and
settings.google_drive_refresh_token and
settings.google_drive_folder_id)
# Or check for service account configuration
else:
return (settings.google_drive_credentials_json and
settings.google_drive_folder_id)
def _should_upload_to_webdav():
return (settings.webdav_url and
@@ -59,18 +69,51 @@ def _should_upload_to_email():
def _should_upload_to_onedrive():
return (settings.onedrive_client_id and
settings.onedrive_client_secret and
(settings.onedrive_refresh_token or
(settings.onedrive_tenant_id and settings.onedrive_tenant_id != "common")))
settings.onedrive_refresh_token)
def _should_upload_to_s3():
return (settings.s3_bucket_name and
settings.aws_access_key_id and
settings.aws_secret_access_key)
@celery.task(base=BaseTaskWithRetry)
def send_to_all_destinations(file_path: str):
"""Distribute a file to all configured storage destinations."""
def get_configured_services_from_validator():
"""
Use the config validator to determine which services are configured properly.
Returns a dictionary with service names as keys and boolean values indicating
whether they're properly configured.
"""
providers = get_provider_status()
service_map = {
"Dropbox": "dropbox",
"NextCloud": "nextcloud",
"Paperless-ngx": "paperless",
"Google Drive": "google_drive",
"WebDAV": "webdav",
"FTP Storage": "ftp",
"SFTP Storage": "sftp",
"Email": "email",
"OneDrive": "onedrive",
"S3 Storage": "s3"
}
result = {}
for provider_name, internal_name in service_map.items():
if provider_name in providers:
result[internal_name] = providers[provider_name].get('configured', False)
return result
@celery.task(base=BaseTaskWithRetry)
def send_to_all_destinations(file_path: str, use_validator=True):
"""
Distribute a file to all configured storage destinations.
Args:
file_path: Path to the file to distribute
use_validator: Whether to use the config validator to determine enabled services
(if False, falls back to individual checks)
"""
if not os.path.exists(file_path):
raise FileNotFoundError(f"File not found: {file_path}")
@@ -131,12 +174,42 @@ def send_to_all_destinations(file_path: str):
},
]
# Optionally get configuration status from validator
configured_services = {}
if use_validator:
try:
configured_services = get_configured_services_from_validator()
logger.info(f"Configured services according to validator: {configured_services}")
except Exception as e:
logger.warning(f"Failed to get configuration from validator: {str(e)}")
use_validator = False
# Process each service
for service in services:
if service["should_upload"]():
logger.info(f"Queueing {file_path} for {service['name']} upload")
task = service["upload_func"].delay(file_path)
results[f"{service['name']}_task_id"] = task.id
service_name = service["name"]
# Determine if service is configured
is_configured = False
if use_validator and service_name in configured_services:
is_configured = configured_services[service_name]
logger.debug(f"{service_name} configuration from validator: {is_configured}")
else:
try:
is_configured = service["should_upload"]()
logger.debug(f"{service_name} configuration from function: {is_configured}")
except Exception as e:
logger.error(f"Error checking configuration for {service_name}: {str(e)}")
is_configured = False
# Queue the upload task if service is configured
if is_configured:
logger.info(f"Queueing {file_path} for {service_name} upload")
try:
task = service["upload_func"].delay(file_path)
results[f"{service_name}_task_id"] = task.id
except Exception as e:
logger.error(f"Failed to queue {service_name} task: {str(e)}")
results[f"{service_name}_error"] = str(e)
return {
"status": "Queued",
+46
View File
@@ -8,6 +8,10 @@ import logging
from googleapiclient.discovery import build
from googleapiclient.http import MediaFileUpload
from google.oauth2.service_account import Credentials
from google.oauth2.credentials import Credentials as OAuthCredentials
from google_auth_oauthlib.flow import Flow
from google.auth.transport.requests import Request
from google.auth.exceptions import RefreshError
from app.config import settings
from app.tasks.retry_config import BaseTaskWithRetry
@@ -15,12 +19,54 @@ from app.celery_app import celery
logger = logging.getLogger(__name__)
def get_drive_service_oauth():
"""
Get Google Drive service using OAuth credentials.
Uses saved refresh token to get a new access token.
"""
try:
# Check for required OAuth settings
if not (settings.google_drive_client_id and
settings.google_drive_client_secret and
settings.google_drive_refresh_token):
logger.error("Google Drive OAuth credentials not fully configured")
return None
# Create credentials object from refresh token
credentials = OAuthCredentials(
None, # No access token initially, will be refreshed
refresh_token=settings.google_drive_refresh_token,
token_uri="https://oauth2.googleapis.com/token",
client_id=settings.google_drive_client_id,
client_secret=settings.google_drive_client_secret,
# Use only drive.file scope
scopes=['https://www.googleapis.com/auth/drive.file']
)
# Refresh the access token
credentials.refresh(Request())
# Build and return the service
service = build('drive', 'v3', credentials=credentials)
return service
except RefreshError as e:
logger.error(f"Failed to refresh Google Drive token: {str(e)}")
raise
except Exception as e:
logger.error(f"Failed to authenticate with Google Drive OAuth: {str(e)}")
return None
def get_google_drive_service():
"""
Authenticate with Google Drive API using service account credentials
and return an authorized service object.
"""
try:
# Check if we should use OAuth instead of service account
if getattr(settings, 'google_drive_use_oauth', False):
return get_drive_service_oauth()
# Load service account credentials from settings
if not settings.google_drive_credentials_json:
logger.error("Google Drive credentials not configured")