fix(config): derive mode flags in Settings.__post_init__; address review round 2
The integration jobs for `mcp-multi-user-basic` and `mcp-login-flow`
were failing with HTTP 500s. Root cause: `get_settings()` builds a
fresh Settings on every call (not cached). Commits 3 and 4 set the
derived `enable_login_flow` / `enable_multi_user_basic_auth` flags as
a side effect of `detect_auth_mode`. detect_auth_mode runs once at
startup, against the Settings instance owned by `validate_configuration`.
Every per-request call site that does `settings = get_settings()` got
a fresh Settings with both flags at their default `False` (since the
env-var aliases were dropped), causing the multi-user dispatcher in
`context.py` to take the wrong branch and crash.
Fix: move the derivation into `Settings.__post_init__`. Every Settings
instance now carries correct flags from the moment it's constructed —
no caching needed, no mutation-after-construction race. detect_auth_mode
becomes a pure reader of the already-derived state.
The legacy env-var deprecation check moves with it. It also picks up
the reviewer's truthy-string fix: previously `os.getenv(legacy)` fired
for the literal string "false" (a non-empty Python string is truthy),
which would have errored on any user with a leftover
`ENABLE_LOGIN_FLOW=false` in their `.env`. The check now only fires
when the value lowercases to one of {"1", "true", "yes", "on"}.
- nextcloud_mcp_server/config.py: extend Settings.__post_init__ with
the legacy-deprecation block and the derived-flag derivation
(resolve mode from deployment_mode + username/password, set flags).
- nextcloud_mcp_server/config_validators.py: drop the
`_sync_derived_flags` helper (superseded by __post_init__). Drop the
legacy-env-var deprecation block (moved). `detect_auth_mode` is now
pure — no mutation. Drop the now-unused `import os`.
- tests/unit/test_config_validators.py: legacy-env-var tests now
expect `ValueError` at `Settings(...)` construction (via `get_settings()`),
not at `detect_auth_mode` call. Added two new tests:
* `test_legacy_env_var_check_ignores_falsy_strings` — pins the
truthy-string fix (reviewer round 2 finding).
* `test_derived_flags_stable_across_get_settings_calls` — regression
test pinning the integration-test fix (two consecutive
`get_settings()` calls return Settings instances with the same
derived flags).
Also reworked `test_login_flow_mode_auto_derives_enable_login_flow_flag`
to assert at-construction derivation (not the old mutation pattern).
- docs/configuration-migration-v2.md: dropped the duplicate
`MCP_DEPLOYMENT_MODE=multi_user_basic` line (review round 2 nit — a
sed artifact from commit 4).
- docs/ADR-021-configuration-consolidation.md: sed-replaced the in-body
`MCP_DEPLOYMENT_MODE=oauth_single_audience` examples with `login_flow`
(review round 2 nit — only the status header was updated in commit 4).
- tests/conftest.py: docstring comment for the multi-user-basic fixture
switched from `ENABLE_MULTI_USER_BASIC_AUTH=true` to
`MCP_DEPLOYMENT_MODE=multi_user_basic` (review round 2 nit).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
282c245da1
commit
6e7c821761
@@ -9,7 +9,6 @@ See ADR-020 for detailed architecture and deployment mode documentation.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
@@ -163,11 +162,16 @@ def detect_auth_mode(settings: Settings) -> AuthMode:
|
||||
"""Detect authentication mode from configuration.
|
||||
|
||||
Mode detection priority (ADR-021, updated for ADR-022):
|
||||
0. Explicit MCP_DEPLOYMENT_MODE (if set) - NEW in ADR-021
|
||||
1. Multi-user BasicAuth
|
||||
2. Single-user BasicAuth
|
||||
0. Explicit MCP_DEPLOYMENT_MODE (if set) — NEW in ADR-021
|
||||
1. Multi-user BasicAuth (only via explicit mode after ADR-022 follow-up)
|
||||
2. Single-user BasicAuth (auto-detected from credentials)
|
||||
3. Login Flow v2 (default — was OAuth single-audience pre-ADR-022)
|
||||
|
||||
Pure function — the legacy-env-var deprecation and the derivation of
|
||||
`enable_login_flow` / `enable_multi_user_basic_auth` now happen in
|
||||
`Settings.__post_init__` so every Settings instance carries correct
|
||||
flags regardless of how it was constructed.
|
||||
|
||||
Args:
|
||||
settings: Application settings
|
||||
|
||||
@@ -175,31 +179,15 @@ def detect_auth_mode(settings: Settings) -> AuthMode:
|
||||
Detected AuthMode
|
||||
|
||||
Raises:
|
||||
ValueError: If explicit deployment_mode is invalid or conflicts with detected mode
|
||||
ValueError: If explicit deployment_mode is unrecognised.
|
||||
"""
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ADR-022 follow-up: fail loudly if a caller is still relying on the
|
||||
# removed env-var aliases. Bypass dynaconf and read os.environ directly
|
||||
# so the check survives even though the aliases are gone.
|
||||
for legacy, replacement in (
|
||||
("ENABLE_MULTI_USER_BASIC_AUTH", "multi_user_basic"),
|
||||
("ENABLE_LOGIN_FLOW", "login_flow"),
|
||||
):
|
||||
if os.getenv(legacy):
|
||||
raise ValueError(
|
||||
f"{legacy} is no longer read from the environment. "
|
||||
f"Set MCP_DEPLOYMENT_MODE={replacement} instead "
|
||||
"(ADR-022). The deployment mode is the single source of "
|
||||
"truth for selecting an auth flow."
|
||||
)
|
||||
|
||||
# ADR-021: Check for explicit deployment mode first
|
||||
# ADR-021: explicit deployment mode wins
|
||||
if settings.deployment_mode:
|
||||
mode_str = settings.deployment_mode.lower().strip()
|
||||
|
||||
# Map string to AuthMode enum
|
||||
mode_map = {
|
||||
"single_user_basic": AuthMode.SINGLE_USER_BASIC,
|
||||
"multi_user_basic": AuthMode.MULTI_USER_BASIC,
|
||||
@@ -215,41 +203,22 @@ def detect_auth_mode(settings: Settings) -> AuthMode:
|
||||
|
||||
explicit_mode = mode_map[mode_str]
|
||||
logger.info(f"Using explicit deployment mode: {explicit_mode.value}")
|
||||
_sync_derived_flags(settings, explicit_mode)
|
||||
return explicit_mode
|
||||
|
||||
# Auto-detection (no explicit deployment_mode).
|
||||
# MULTI_USER_BASIC is no longer auto-detectable — the ENABLE_MULTI_USER_BASIC_AUTH
|
||||
# env-var alias was dropped in the ADR-022 follow-up, so the only way to
|
||||
# opt into that mode is `MCP_DEPLOYMENT_MODE=multi_user_basic` (handled
|
||||
# above). The legacy env var fails loudly at the top of this function.
|
||||
# env-var alias was dropped in the ADR-022 follow-up, so the only way
|
||||
# to opt into that mode is `MCP_DEPLOYMENT_MODE=multi_user_basic`
|
||||
# (handled above). The legacy env var fails loudly in
|
||||
# `Settings.__post_init__`.
|
||||
|
||||
# Check for single-user BasicAuth (explicit credentials)
|
||||
if settings.nextcloud_username and settings.nextcloud_password:
|
||||
_sync_derived_flags(settings, AuthMode.SINGLE_USER_BASIC)
|
||||
return AuthMode.SINGLE_USER_BASIC
|
||||
|
||||
# Default: Login Flow v2 multi-user mode (browser-based app-password flow).
|
||||
# The un-augmented OAuth bearer pass-through it replaced required unmerged
|
||||
# Nextcloud user_oidc patches (see ADR-022).
|
||||
_sync_derived_flags(settings, AuthMode.LOGIN_FLOW)
|
||||
return AuthMode.LOGIN_FLOW
|
||||
|
||||
|
||||
def _sync_derived_flags(settings: Settings, mode: AuthMode) -> None:
|
||||
"""Derive internal feature flags from the resolved deployment mode.
|
||||
|
||||
Some runtime call sites (app.py, context.py, auth/scope_authorization.py)
|
||||
still read individual boolean flags rather than passing the mode around.
|
||||
Keep those flags in sync with the mode here so the mode is the single
|
||||
source of truth and users don't have to set redundant env vars. The
|
||||
ENABLE_LOGIN_FLOW and ENABLE_MULTI_USER_BASIC_AUTH env-var aliases were
|
||||
removed in the ADR-022 follow-up (PR #787).
|
||||
"""
|
||||
settings.enable_login_flow = mode == AuthMode.LOGIN_FLOW
|
||||
settings.enable_multi_user_basic_auth = mode == AuthMode.MULTI_USER_BASIC
|
||||
|
||||
|
||||
def validate_configuration(settings: Settings) -> tuple[AuthMode, list[str]]:
|
||||
"""Validate configuration for detected mode.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user