feat(config): enable DCR for multi-user BasicAuth with offline access
Allows multi-user BasicAuth mode to use Dynamic Client Registration (DCR) for OAuth credentials when ENABLE_OFFLINE_ACCESS is enabled, making it consistent with OAuth modes and reducing configuration burden. **Changes:** Configuration Validation: - Relaxed OAuth credential requirements for multi-user BasicAuth - OAuth credentials now optional when offline access enabled - Will use DCR as fallback if NEXTCLOUD_OIDC_CLIENT_ID/SECRET not set - Updated validation to log info instead of error when DCR will be used Startup Logic (app.py): - Added DCR workflow for multi-user BasicAuth before uvicorn starts - Creates oauth_context for management APIs when offline access enabled - Allows Astrolabe to authenticate management API calls with OAuth - DCR runs synchronously at same lifecycle point as OAuth modes - Added traceback import for better error logging - Fixed type assertions for nextcloud_host - Fixed undefined variable references in vector sync logging Management API: - Improved auth mode detection using proper detect_auth_mode() - Added auth_mode field to /status endpoint: * "basic" - Single-user BasicAuth * "multi_user_basic" - Multi-user BasicAuth * "oauth" - OAuth modes * "smithery" - Smithery stateless - Added supports_app_passwords indicator for multi-user BasicAuth Docker Compose: - Updated mcp-multi-user-basic service configuration: * Enabled vector sync (VECTOR_SYNC_ENABLED=true) * Added ENABLE_OFFLINE_ACCESS=true for app password support * Added NEXTCLOUD_MCP_SERVER_URL for Astrolabe integration * Documented optional static OAuth credentials Testing: - Updated test_config_validators.py to expect DCR fallback - Enhanced configure_astrolabe_for_mcp_server fixture with verification - Added debug logging to test_users_setup fixture **Workflow:** 1. User configures ENABLE_OFFLINE_ACCESS=true 2. Server checks for static NEXTCLOUD_OIDC_CLIENT_ID/SECRET 3. If not found, performs DCR before uvicorn starts 4. DCR registers client with Nextcloud OIDC provider 5. OAuth credentials used for Astrolabe management API auth 6. Background sync can retrieve user app passwords via Astrolabe 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
+50
-6
@@ -2320,7 +2320,10 @@ async def test_users_setup(anyio_backend, nc_client: NextcloudClient):
|
||||
},
|
||||
}
|
||||
|
||||
logger.info("Creating test users for multi-user OAuth testing...")
|
||||
logger.info("=" * 60)
|
||||
logger.info("EXECUTING test_users_setup FIXTURE (session-scoped)")
|
||||
logger.info(f"Creating test users: {list(test_user_configs.keys())}")
|
||||
logger.info("=" * 60)
|
||||
created_users = []
|
||||
|
||||
try:
|
||||
@@ -3267,7 +3270,7 @@ async def configure_astrolabe_for_mcp_server(nc_client):
|
||||
)
|
||||
|
||||
# Configure MCP server URLs in Nextcloud system config
|
||||
subprocess.run(
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"compose",
|
||||
@@ -3281,11 +3284,45 @@ async def configure_astrolabe_for_mcp_server(nc_client):
|
||||
"--value",
|
||||
mcp_server_internal_url,
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
subprocess.run(
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"Failed to configure MCP server URL. "
|
||||
f"Command failed with code {result.returncode}. "
|
||||
f"stderr: {result.stderr}, stdout: {result.stdout}"
|
||||
)
|
||||
|
||||
# Verify mcp_server_url was actually set
|
||||
verify_result = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"compose",
|
||||
"exec",
|
||||
"-T",
|
||||
"app",
|
||||
"php",
|
||||
"/var/www/html/occ",
|
||||
"config:system:get",
|
||||
"mcp_server_url",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
actual_url = verify_result.stdout.strip()
|
||||
if actual_url != mcp_server_internal_url:
|
||||
raise RuntimeError(
|
||||
f"MCP server URL verification failed. "
|
||||
f"Expected: {mcp_server_internal_url}, Got: {actual_url}"
|
||||
)
|
||||
|
||||
logger.info(f"✓ MCP server URL configured and verified: {actual_url}")
|
||||
|
||||
# Configure public URL
|
||||
result = subprocess.run(
|
||||
[
|
||||
"docker",
|
||||
"compose",
|
||||
@@ -3299,11 +3336,18 @@ async def configure_astrolabe_for_mcp_server(nc_client):
|
||||
"--value",
|
||||
mcp_server_public_url,
|
||||
],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
)
|
||||
|
||||
logger.info("✓ MCP server URLs configured")
|
||||
if result.returncode != 0:
|
||||
raise RuntimeError(
|
||||
f"Failed to configure MCP server public URL. "
|
||||
f"Command failed with code {result.returncode}. "
|
||||
f"stderr: {result.stderr}, stdout: {result.stdout}"
|
||||
)
|
||||
|
||||
logger.info(f"✓ MCP server public URL configured: {mcp_server_public_url}")
|
||||
|
||||
# Remove existing OAuth client if it exists
|
||||
try:
|
||||
|
||||
@@ -281,7 +281,7 @@ class TestMultiUserBasicValidation:
|
||||
assert any("nextcloud_password" in err.lower() for err in errors)
|
||||
|
||||
def test_offline_access_missing_oauth_credentials(self):
|
||||
"""Test error when offline access enabled but OAuth credentials missing."""
|
||||
"""Test that offline access works without OAuth credentials (will use DCR)."""
|
||||
settings = Settings(
|
||||
nextcloud_host="http://localhost",
|
||||
enable_multi_user_basic_auth=True,
|
||||
@@ -293,7 +293,8 @@ class TestMultiUserBasicValidation:
|
||||
mode, errors = validate_configuration(settings)
|
||||
|
||||
assert mode == AuthMode.MULTI_USER_BASIC
|
||||
assert any("oidc_client_id" in err.lower() for err in errors)
|
||||
# No errors - DCR will be used as fallback (consistent with OAuth modes)
|
||||
assert len(errors) == 0
|
||||
|
||||
def test_offline_access_missing_encryption_key(self):
|
||||
"""Test error when offline access enabled but encryption key missing."""
|
||||
|
||||
Reference in New Issue
Block a user