diff --git a/nextcloud_mcp_server/api/management.py b/nextcloud_mcp_server/api/management.py index 0723822..efbb8c0 100644 --- a/nextcloud_mcp_server/api/management.py +++ b/nextcloud_mcp_server/api/management.py @@ -11,7 +11,6 @@ The PHP app obtains tokens through PKCE flow and uses them to access these endpo """ import logging -import os import time from importlib.metadata import version from typing import Any @@ -347,11 +346,12 @@ async def get_user_session(request: Request) -> JSONResponse: ) # Check if offline access is enabled - enable_offline_access = os.getenv("ENABLE_OFFLINE_ACCESS", "false").lower() in ( - "true", - "1", - "yes", - ) + # Use settings.enable_offline_access which handles both ENABLE_BACKGROUND_OPERATIONS (new) + # and ENABLE_OFFLINE_ACCESS (deprecated) environment variables + from nextcloud_mcp_server.config import get_settings + + settings = get_settings() + enable_offline_access = settings.enable_offline_access if not enable_offline_access: # Offline access disabled - return minimal session info diff --git a/nextcloud_mcp_server/app.py b/nextcloud_mcp_server/app.py index 9b33912..2ae3665 100644 --- a/nextcloud_mcp_server/app.py +++ b/nextcloud_mcp_server/app.py @@ -539,11 +539,10 @@ async def load_oauth_client_credentials( dcr_scopes = "openid profile email notes:read notes:write calendar:read calendar:write todo:read todo:write contacts:read contacts:write cookbook:read cookbook:write deck:read deck:write tables:read tables:write files:read files:write sharing:read sharing:write news:read news:write" # Add offline_access scope if refresh tokens are enabled - enable_offline_access = os.getenv("ENABLE_OFFLINE_ACCESS", "false").lower() in ( - "true", - "1", - "yes", - ) + # Use settings.enable_offline_access which handles both ENABLE_BACKGROUND_OPERATIONS (new) + # and ENABLE_OFFLINE_ACCESS (deprecated) environment variables + dcr_settings = get_settings() + enable_offline_access = dcr_settings.enable_offline_access if enable_offline_access: dcr_scopes = f"{dcr_scopes} offline_access" logger.info("✓ offline_access scope enabled for refresh tokens") @@ -672,6 +671,10 @@ async def setup_oauth_config(): Returns: Tuple of (nextcloud_host, token_verifier, auth_settings, refresh_token_storage, oauth_client, oauth_provider, client_id, client_secret) """ + # Get settings for enable_offline_access check (handles both ENABLE_BACKGROUND_OPERATIONS + # and ENABLE_OFFLINE_ACCESS environment variables) + settings = get_settings() + nextcloud_host = os.getenv("NEXTCLOUD_HOST") if not nextcloud_host: raise ValueError( @@ -744,11 +747,9 @@ async def setup_oauth_config(): logger.info("✓ Detected integrated mode (Nextcloud OIDC app)") # Check if offline access (refresh tokens) is enabled - enable_offline_access = os.getenv("ENABLE_OFFLINE_ACCESS", "false").lower() in ( - "true", - "1", - "yes", - ) + # Use settings.enable_offline_access which handles both ENABLE_BACKGROUND_OPERATIONS (new) + # and ENABLE_OFFLINE_ACCESS (deprecated) environment variables + enable_offline_access = settings.enable_offline_access # Initialize refresh token storage if enabled refresh_token_storage = None @@ -799,8 +800,10 @@ async def setup_oauth_config(): ) # ADR-005: Unified Token Verifier with proper audience validation - # Use discovered issuer for JWT validation - client_issuer = issuer + # Use public issuer URL for JWT validation if set (handles Docker internal/external URL mismatch) + # Tokens are issued with the public URL, but OIDC discovery returns internal URL + public_issuer_url = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL") + client_issuer = public_issuer_url if public_issuer_url else issuer # Get MCP server URL for audience validation mcp_server_url = os.getenv("NEXTCLOUD_MCP_SERVER_URL", "http://localhost:8000") nextcloud_resource_uri = os.getenv("NEXTCLOUD_RESOURCE_URI", nextcloud_host) @@ -817,10 +820,8 @@ async def setup_oauth_config(): "This should be set explicitly for proper audience validation." ) - # Create settings for UnifiedTokenVerifier - from nextcloud_mcp_server.config import get_settings - - settings = get_settings() + # Create settings for UnifiedTokenVerifier (use same settings instance from start of function) + # settings is already set at the start of setup_oauth_config() # Override with discovered values if not set in environment if not settings.oidc_client_id: settings.oidc_client_id = client_id @@ -1015,8 +1016,10 @@ async def setup_oauth_config_for_multi_user_basic( mcp_server_url = os.getenv("NEXTCLOUD_MCP_SERVER_URL", "http://localhost:8000") nextcloud_resource_uri = os.getenv("NEXTCLOUD_RESOURCE_URI", nextcloud_host) - # Use discovered issuer for JWT validation - client_issuer = issuer + # Use public issuer URL for JWT validation if set (handles Docker internal/external URL mismatch) + # Tokens are issued with the public URL, but OIDC discovery returns internal URL + public_issuer_url = os.getenv("NEXTCLOUD_PUBLIC_ISSUER_URL") + client_issuer = public_issuer_url if public_issuer_url else issuer # Update settings with discovered values for UnifiedTokenVerifier if not settings.oidc_client_id: @@ -1399,13 +1402,9 @@ def get_app(transport: str = "streamable-http", enabled_apps: list[str] | None = enable_token_exchange = ( os.getenv("ENABLE_TOKEN_EXCHANGE", "false").lower() == "true" ) - enable_offline_access_for_tools = os.getenv( - "ENABLE_OFFLINE_ACCESS", "false" - ).lower() in ( - "true", - "1", - "yes", - ) + # Use settings.enable_offline_access which handles both ENABLE_BACKGROUND_OPERATIONS (new) + # and ENABLE_OFFLINE_ACCESS (deprecated) environment variables + enable_offline_access_for_tools = settings.enable_offline_access if oauth_enabled and enable_offline_access_for_tools and not enable_token_exchange: logger.info("Registering OAuth provisioning tools for offline access") register_oauth_tools(mcp) diff --git a/nextcloud_mcp_server/auth/scope_authorization.py b/nextcloud_mcp_server/auth/scope_authorization.py index 65cc811..9475e6a 100644 --- a/nextcloud_mcp_server/auth/scope_authorization.py +++ b/nextcloud_mcp_server/auth/scope_authorization.py @@ -1,7 +1,6 @@ """Scope-based authorization for MCP tools.""" import logging -import os from functools import wraps from typing import Any, Callable @@ -131,9 +130,12 @@ def require_scopes(*required_scopes: str): required_scopes_set = set(required_scopes) # Check if offline access is enabled - enable_offline_access = ( - os.getenv("ENABLE_OFFLINE_ACCESS", "false").lower() == "true" - ) + # Use settings.enable_offline_access which handles both ENABLE_BACKGROUND_OPERATIONS (new) + # and ENABLE_OFFLINE_ACCESS (deprecated) environment variables + from nextcloud_mcp_server.config import get_settings + + settings = get_settings() + enable_offline_access = settings.enable_offline_access # In offline access mode, check if Nextcloud scopes require provisioning if enable_offline_access: diff --git a/nextcloud_mcp_server/server/oauth_tools.py b/nextcloud_mcp_server/server/oauth_tools.py index 02cbb25..132e38a 100644 --- a/nextcloud_mcp_server/server/oauth_tools.py +++ b/nextcloud_mcp_server/server/oauth_tools.py @@ -303,16 +303,17 @@ async def provision_nextcloud_access( ), ) - # Get configuration - enable_offline_access = ( - os.getenv("ENABLE_OFFLINE_ACCESS", "false").lower() == "true" - ) - if not enable_offline_access: + # Get configuration using settings (handles both ENABLE_BACKGROUND_OPERATIONS + # and ENABLE_OFFLINE_ACCESS environment variables) + from nextcloud_mcp_server.config import get_settings + + settings = get_settings() + if not settings.enable_offline_access: return ProvisioningResult( success=False, message=( "Offline access is not enabled. " - "Set ENABLE_OFFLINE_ACCESS=true to use this feature." + "Set ENABLE_BACKGROUND_OPERATIONS=true to use this feature." ), ) @@ -488,13 +489,14 @@ async def check_logged_in(ctx: Context, user_id: Optional[str] = None) -> str: logger.info("=" * 60) # Not logged in - generate OAuth URL for Flow 2 - enable_offline_access = ( - os.getenv("ENABLE_OFFLINE_ACCESS", "false").lower() == "true" - ) - if not enable_offline_access: + # Use settings (handles both ENABLE_BACKGROUND_OPERATIONS and ENABLE_OFFLINE_ACCESS) + from nextcloud_mcp_server.config import get_settings + + settings = get_settings() + if not settings.enable_offline_access: return ( "Not logged in. Offline access is not enabled. " - "Set ENABLE_OFFLINE_ACCESS=true to use this feature." + "Set ENABLE_BACKGROUND_OPERATIONS=true to use this feature." ) # Get MCP server's OAuth client credentials