Files
nextcloud-mcp-server/docs/testing-oidc-consent.md
2025-10-24 04:38:49 +02:00

12 KiB

Testing OIDC Consent Feature

This guide explains how to test the OIDC consent feature using the development version of the OIDC app mounted into the Docker environment.

Setup

Volume Mount Configuration

The development OIDC app is mounted from ~/Software/oidc into the container at /opt/apps/oidc:

# docker-compose.yml
volumes:
  - ../Software/oidc:/opt/apps/oidc:ro

Why mount outside /var/www/html/?

  • The Nextcloud container uses rsync to initialize /var/www/html/ from the image
  • Mounting inside that path causes conflicts (rsync tries to delete mounted directories)
  • Mounting to /opt/apps/oidc avoids rsync entirely
  • Nextcloud supports multiple app directories via the apps_paths configuration

How multiple app paths work:

  • Nextcloud can load apps from multiple directories
  • The post-installation hook registers /opt/apps as an additional app directory (index 2)
  • Apps in default paths (index 0 and 1) are still available
  • All directories are scanned for apps, but /opt/apps is read-only

This setup allows you to:

  • Test changes without rebuilding containers
  • Avoid needing npm/node in the container (JS already built on host)
  • Iterate quickly on development
  • Install other Nextcloud apps normally (custom_apps remains writable)

How It Works

  1. Mount Development App: Docker mounts ~/Software/oidc to /opt/apps/oidc (outside Nextcloud's path)
  2. Register App Path: The 10-install-oidc-app.sh hook configures /opt/apps as an additional app directory
  3. Enable App: The hook enables the OIDC app from /opt/apps/oidc
  4. Run Migrations: Nextcloud detects pending migrations and runs them automatically
  5. Configure OIDC: Dynamic client registration and PKCE are enabled

Starting the Stack

cd ~/Projects/nextcloud-mcp-server

# Start fresh (recommended for first test)
docker compose down -v
docker compose up -d

# Wait for initialization (check logs)
docker compose logs -f app

The post-installation hooks will:

  1. Configure custom_apps path (already done)
  2. Enable OIDC app from mounted directory
  3. Run database migrations (including consent table creation)
  4. Configure OIDC settings

Verifying Installation

Before Container Restart

Before running docker compose up -d, the consent feature will NOT be active:

  • No oc_oidc_user_consents table in database
  • Migration 0015 not applied yet
  • ConsentController class not loaded
  • Consent routes not registered

You can verify this with:

# Check migrations applied (should stop at 0014)
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT version FROM oc_migrations WHERE app = 'oidc' ORDER BY version DESC LIMIT 3;" nextcloud

# Check for consent table (should return empty)
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SHOW TABLES LIKE 'oc_oidc_user_consents';" nextcloud

After Container Restart

After docker compose up -d with the mounted OIDC directory, the consent feature should be active:

  • oc_oidc_user_consents table exists
  • Migration 0015 (Version0015Date20251123100100) applied
  • ConsentController routes registered
  • Consent screen appears during OAuth flows

Check App Status

docker compose exec app php occ app:list | grep -A 2 oidc

Expected output:

  - oidc: 1.10.0 (enabled)

Verify App Paths Configuration

Verify that /opt/apps is registered as an additional app directory:

# Check configured app paths
docker compose exec app php occ config:system:get apps_paths

# Verify the mount is accessible
docker compose exec app ls -la /opt/apps/oidc/

# Verify custom_apps is writable (for normal app installation)
docker compose exec -u www-data app touch /var/www/html/custom_apps/.test && echo "✅ custom_apps is writable" || echo "❌ custom_apps NOT writable"
docker compose exec app rm -f /var/www/html/custom_apps/.test

Expected: Output should show multiple app paths including index 2 (/opt/apps).

# Check controller exists in mounted location
docker compose exec app ls -la /opt/apps/oidc/lib/Controller/ConsentController.php

# Check Vue component exists
docker compose exec app ls -la /opt/apps/oidc/src/Consent.vue

# Check built JS exists
docker compose exec app ls -lh /opt/apps/oidc/js/oidc-consent.js

Verify Database Migration

Note: These checks will only pass after restarting containers with the mounted OIDC app.

# Check if consent table exists
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SHOW TABLES LIKE 'oc_oidc_user_consents';"

# Check table structure
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "DESCRIBE oc_oidc_user_consents;"

# Verify migration 0015 was applied
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT app, version FROM oc_migrations WHERE app = 'oidc' AND version LIKE '%0015%';"

Expected table structure:

  • id: int(10) unsigned, auto_increment, primary key
  • user_id: varchar(256), not null
  • client_id: int(10) unsigned, not null
  • scopes_granted: varchar(512), not null
  • created_at: int(10) unsigned, not null
  • updated_at: int(10) unsigned, not null
  • expires_at: int(10) unsigned, nullable

Verify Routes

docker compose exec app php occ router:list | grep consent

Expected output:

oidc.Consent.show        GET    apps/oidc/consent
oidc.Consent.grant       POST   apps/oidc/consent/grant
oidc.Consent.deny        POST   apps/oidc/consent/deny

1. Create an OAuth Client

The JWT client is automatically created by the post-installation hooks:

# Check if JWT client exists
docker compose exec app cat /var/www/html/.oauth-jwt/nextcloud_oauth_client.json

2. Initiate Authorization Flow

You can test using the MCP OAuth container or manually:

Option A: Using MCP OAuth container

# The mcp-oauth container will trigger the OAuth flow
docker compose logs -f mcp-oauth

Option B: Manual browser test

  1. Get client_id from the JWT client JSON
  2. Visit in browser:
http://localhost:8080/apps/oidc/authorize?client_id=YOUR_CLIENT_ID&response_type=code&redirect_uri=http://localhost:8001/oauth/callback&scope=openid+profile+email+mcp:notes:read+mcp:notes:write&state=test123

3. Expected Behavior

First Authorization:

  1. User logs in (if not already authenticated)
  2. Consent screen appears with:
    • Application name: "Nextcloud MCP Server JWT"
    • List of requested scopes with descriptions:
      • ✓ Basic authentication (openid) - required, cannot deselect
      • ✓ Profile information (profile)
      • ✓ Email address (email)
      • ✓ mcp:notes:read (custom scope, shown as-is)
      • ✓ mcp:notes:write (custom scope, shown as-is)
    • "Allow" and "Deny" buttons
  3. User selects scopes and clicks "Allow"
  4. Authorization proceeds with selected scopes
  5. Consent is stored in database

Subsequent Authorizations:

  • Same scopes → No consent screen (uses stored consent)
  • Different scopes → Consent screen appears again
  • If user clicks "Deny" → Returns error=access_denied to client

After granting consent:

# View all stored consents with formatted timestamps
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "
SELECT
    user_id,
    client_id,
    scopes_granted,
    FROM_UNIXTIME(created_at) as created,
    FROM_UNIXTIME(updated_at) as updated,
    FROM_UNIXTIME(expires_at) as expires
FROM oc_oidc_user_consents;
" nextcloud

# Or for a compact view:
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT * FROM oc_oidc_user_consents;" nextcloud

Troubleshooting

Check browser console (F12 → Console tab):

# Look for JS errors like:
Failed to load resource: js/oidc-consent.js

Check Nextcloud logs:

docker compose exec app tail -f /var/www/html/data/nextcloud.log | grep -i consent

Verify JS file loaded:

# Check file exists and has correct size (~73KB)
docker compose exec app ls -lh /opt/apps/oidc/js/oidc-consent.js

Clear Nextcloud caches:

docker compose exec app php occ maintenance:repair
docker compose restart app

Migration Didn't Run

Check which migrations have been applied:

docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT app, version FROM oc_migrations WHERE app = 'oidc' ORDER BY version;" nextcloud

Expected to see Version0015Date20251123100100 in the list.

Manually trigger migrations:

# Disable and re-enable app (triggers all pending migrations)
docker compose exec app php occ app:disable oidc
docker compose exec app php occ app:enable oidc

# Verify migration 0015 was applied
docker compose exec -T db mariadb -u nextcloud -ppassword nextcloud -e "SELECT version FROM oc_migrations WHERE app = 'oidc' AND version LIKE '%0015%';" nextcloud

Routes Not Registered

If router:list doesn't show consent routes:

# The autoloader might not have picked up new classes
# Restart the container
docker compose restart app

# Wait for it to be ready
sleep 10

# Try again
docker compose exec app php occ router:list | grep consent

If still not working, check if ConsentController is accessible:

docker compose exec app php -r "
require_once '/var/www/html/lib/base.php';
\$class = 'OCA\\OIDCIdentityProvider\\Controller\\ConsentController';
if (class_exists(\$class)) {
    echo \"Class exists\n\";
} else {
    echo \"Class not found\n\";
}
"

Making Changes

Frontend Changes (Vue.js)

  1. Edit source file on host:
cd ~/Software/oidc
# Edit src/Consent.vue
  1. Rebuild JS:
npm run build
  1. Refresh browser (container sees changes immediately via volume mount at /opt/apps/oidc)

Backend Changes (PHP)

  1. Edit files on host:
cd ~/Software/oidc
# Edit lib/Controller/ConsentController.php or other PHP files
  1. Changes are immediately visible (PHP is interpreted, no build step)

  2. For new classes or major changes, restart container:

docker compose restart app

Database Schema Changes

If you modify the migration:

# Changes won't be picked up if migration already ran
# Need to recreate the database:
docker compose down -v  # Removes volumes
docker compose up -d    # Fresh start with clean DB

Cleanup

Reset Everything

cd ~/Projects/nextcloud-mcp-server
docker compose down -v

This removes:

  • All containers
  • Database volume (all data)
  • OAuth client credentials

Keep Data, Restart App

docker compose restart app

This preserves:

  • Database (consents, clients, users)
  • OAuth client credentials

Development Workflow Summary

  1. Make changes in ~/Software/oidc
  2. Build JS if you changed Vue files: npm run build
  3. Test immediately - refresh browser or restart container
  4. No need to rebuild Docker images or reinstall app
  5. Iterate quickly with instant feedback

Production Deployment

When ready to deploy:

  1. Create patch file (already done):

    cd ~/Software/oidc
    git format-patch master --stdout > user-consent-feature.patch
    
  2. Test patch in clean environment:

    # In a production-like environment
    cd /path/to/production/oidc
    git apply user-consent-feature.patch
    npm install
    npm run build
    php occ app:disable oidc
    php occ app:enable oidc
    
  3. Verify migration runs automatically on app enable

  4. Submit pull request to upstream repository