Files
gh-christianlouis-docuelevate/app/utils/oauth_helper.py
copilot-swe-agent[bot] d08040ac4a style: fix all flake8 linter errors across app/ directory
- Run Black formatter and isort on all app/ files
- Remove unused imports (F401) across multiple files
- Add # noqa: F401 for intentional re-exports in celery_worker.py,
  tasks/__init__.py, utils.py, frontend.py, views/base.py
- Fix f-strings without placeholders (F541) in azure.py, notification.py,
  check_credentials.py, upload_to_onedrive.py, settings.py
- Fix bare except (E722) in upload_to_sftp.py
- Fix block comment format (E265) in models.py
- Move imports to top of file to fix E402 in celery_app.py, celery_worker.py
- Fix line-too-long (E501) by wrapping strings in multiple files
- Remove unused variable (F841) in upload_to_nextcloud.py

Co-authored-by: christianlouis <361235+christianlouis@users.noreply.github.com>
2026-02-08 17:42:33 +00:00

105 lines
4.2 KiB
Python

"""
OAuth helper utilities for token exchange operations.
Shared across multiple OAuth providers to reduce code duplication.
"""
import logging
from typing import Any, Dict, Optional
import requests
from fastapi import HTTPException, status
from app.config import settings
logger = logging.getLogger(__name__)
def exchange_oauth_token(
provider_name: str, token_url: str, payload: Dict[str, str], timeout: Optional[int] = None
) -> Dict[str, Any]:
"""
Exchange an authorization code for tokens from an OAuth provider.
This function handles the common OAuth token exchange flow across multiple providers
(OneDrive, Google Drive, Dropbox) with proper error handling and secure logging.
Args:
provider_name: Name of the OAuth provider (for logging)
token_url: OAuth token endpoint URL
payload: Request payload containing client credentials and auth code
timeout: Request timeout in seconds (defaults to settings.http_request_timeout)
Returns:
Dict containing the token response from the provider
Raises:
HTTPException: If token exchange fails or response is invalid
"""
if timeout is None:
timeout = settings.http_request_timeout
try:
logger.info(f"Starting {provider_name} token exchange process")
# SECURITY: Never log sensitive data - only log non-sensitive metadata
safe_info = {
"provider": provider_name,
"token_url": token_url,
"grant_type": payload.get("grant_type", "unknown"),
}
logger.info(f"Token exchange request: {safe_info}")
# Make the token request
logger.info(f"Sending POST request to {provider_name} for token exchange")
response = requests.post(token_url, data=payload, timeout=timeout)
# Check if the request was successful
logger.info(f"Token exchange response status: {response.status_code}")
if response.status_code != 200:
# Log the error response for debugging (without sensitive data)
try:
error_json = response.json()
# Extract only error type, not full details which may contain sensitive info
error_type = error_json.get("error", "unknown_error")
logger.error(f"Token exchange failed with status {response.status_code}: {error_type}")
error_detail = {"error": error_type, "error_description": error_json.get("error_description", "")}
except (ValueError, requests.exceptions.JSONDecodeError) as json_err:
logger.error(f"Failed to parse error response as JSON: {str(json_err)}")
error_detail = {"error": "Unknown error", "status_code": response.status_code}
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST, detail=f"Token exchange failed: {error_detail}"
)
# Parse the token response
token_data = response.json()
# Validate the token response
if "refresh_token" not in token_data:
logger.error(f"{provider_name} returned success but no refresh_token found in response")
raise HTTPException(
status_code=status.HTTP_502_BAD_GATEWAY,
detail=f"{provider_name} OAuth server returned success but no refresh token was included",
)
# Log success with non-sensitive metadata only
logger.info(f"Successfully exchanged authorization code for {provider_name} tokens")
return token_data
except HTTPException:
# Re-raise HTTP exceptions as they already have appropriate status codes
raise
except requests.exceptions.RequestException as e:
logger.exception(f"Network error during {provider_name} token exchange: {str(e)}")
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Failed to connect to {provider_name} OAuth service: {str(e)}",
)
except Exception as e:
logger.exception(f"Unexpected error during {provider_name} token exchange: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f"Failed to exchange token: {str(e)}"
)