fix: Use settings.enable_offline_access for env var consolidation
Migrate all direct ENABLE_OFFLINE_ACCESS environment variable checks to use settings.enable_offline_access, which handles both the new ENABLE_BACKGROUND_OPERATIONS and deprecated ENABLE_OFFLINE_ACCESS vars. Also fixes JWT issuer validation in Docker by using NEXTCLOUD_PUBLIC_ISSUER_URL when set, resolving 401 errors caused by internal/external URL mismatch. Changes: - app.py: Use settings for offline access checks in setup_oauth_config, register_oauth_client, and tool registration - oauth_tools.py: Use settings in provision_nextcloud_access and check_logged_in - management.py: Use settings in get_user_session - scope_authorization.py: Use settings in require_scopes decorator - Remove unused os imports after migration 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
|
||||
+24
-25
@@ -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)
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user