fix: Add support for RFC 7592 client registration and deletion

This commit is contained in:
Chris Coutinho
2025-10-24 19:19:27 +02:00
parent 8e0a4d8ce5
commit 50b69a2531
2 changed files with 117 additions and 123 deletions
+37 -75
View File
@@ -213,11 +213,15 @@ async def test_dcr_register_and_delete_lifecycle(
token_type="Bearer",
)
# Store registration_access_token if present
# Store RFC 7592 fields if present
registration_access_token = full_client_info.get("registration_access_token")
registration_client_uri = full_client_info.get("registration_client_uri")
logger.info(
f"Registration access token present: {registration_access_token is not None}"
)
logger.info(
f"Registration client URI present: {registration_client_uri is not None}"
)
logger.info(f"✅ Client registered: {client_info.client_id[:16]}...")
@@ -237,77 +241,25 @@ async def test_dcr_register_and_delete_lifecycle(
assert access_token, "Failed to obtain access token"
logger.info(f"✅ Access token obtained: {access_token[:30]}...")
# Step 3: Delete the client
# Step 3: Delete the client using RFC 7592
logger.info("Step 3: Deleting OAuth client...")
logger.info(f"Client ID: {client_info.client_id}")
logger.info(f"Client secret (first 16 chars): {client_info.client_secret[:16]}...")
logger.info(
f"Registration access token: {registration_access_token[:16] if registration_access_token else 'None'}..."
)
# First, let's manually test the deletion endpoint with different auth methods
deletion_endpoint = f"{nextcloud_host}/apps/oidc/register/{client_info.client_id}"
logger.info(f"Deletion endpoint: {deletion_endpoint}")
# Test with both authentication methods
async with httpx.AsyncClient(timeout=30.0) as test_client:
# Method 1: HTTP Basic Auth
logger.info("Method 1: Testing deletion with HTTP Basic Auth...")
response_basic = await test_client.delete(
deletion_endpoint,
auth=(client_info.client_id, client_info.client_secret),
)
logger.info(f"HTTP Basic Auth response status: {response_basic.status_code}")
logger.info(f"Response body: {response_basic.text[:200]}")
# Method 2: Credentials in JSON body
logger.info("\nMethod 2: Testing deletion with credentials in JSON body...")
response_json = await test_client.delete(
deletion_endpoint,
json={
"client_id": client_info.client_id,
"client_secret": client_info.client_secret,
},
)
logger.info(f"JSON body response status: {response_json.status_code}")
logger.info(f"Response body: {response_json.text[:200]}")
# Method 3: Try with query parameters
logger.info(
"\nMethod 3: Testing deletion with credentials in query parameters..."
)
response_query = await test_client.delete(
deletion_endpoint,
params={
"client_id": client_info.client_id,
"client_secret": client_info.client_secret,
},
)
logger.info(f"Query params response status: {response_query.status_code}")
logger.info(f"Response body: {response_query.text[:200]}")
# Summary
logger.info("\n=== SUMMARY ===")
logger.info(f"Basic Auth: {response_basic.status_code}")
logger.info(f"JSON Body: {response_json.status_code}")
logger.info(f"Query Params: {response_query.status_code}")
if response_basic.status_code == 401 and response_json.status_code == 401:
logger.info("✗ All authentication methods failed with 401 Unauthorized")
elif (
response_basic.status_code == 204
or response_json.status_code == 204
or response_query.status_code == 204
):
logger.info("✓ At least one authentication method succeeded!")
else:
logger.info("Unexpected status codes - need further investigation")
# Use delete_client() which prefers RFC 7592 Bearer token, falls back to Basic Auth
success = await delete_client(
nextcloud_url=nextcloud_host,
client_id=client_info.client_id,
registration_access_token=registration_access_token,
client_secret=client_info.client_secret,
registration_client_uri=registration_client_uri,
)
assert success, (
"Client deletion should succeed, but got status from manual test above"
"Client deletion should succeed with RFC 7592 Bearer token or Basic Auth"
)
logger.info(f"✅ Client deleted successfully: {client_info.client_id[:16]}...")
@@ -329,8 +281,11 @@ async def test_dcr_register_and_delete_lifecycle(
)
# If we get here, check the status code
if token_response.status_code == 401:
logger.info("✅ Deleted client correctly rejected (401 Unauthorized)")
# Accept either 400 (Bad Request) or 401 (Unauthorized) as valid rejection
if token_response.status_code in [400, 401]:
logger.info(
f"✅ Deleted client correctly rejected ({token_response.status_code})"
)
else:
# Unexpected success - client should be deleted
pytest.fail(
@@ -355,12 +310,12 @@ async def test_dcr_delete_with_wrong_credentials(
oauth_callback_server,
):
"""
Test that deletion fails with wrong client credentials (401 Unauthorized).
Test that deletion fails with wrong registration_access_token (401 Unauthorized).
This verifies:
1. Client registration succeeds
2. Deletion with wrong client_secret returns 401
3. Deletion with correct credentials still works
1. Client registration succeeds and returns registration_access_token
2. Deletion with wrong registration_access_token returns 401
3. Deletion with correct registration_access_token succeeds (RFC 7592)
"""
nextcloud_host = os.getenv("NEXTCLOUD_HOST")
if not nextcloud_host:
@@ -390,25 +345,28 @@ async def test_dcr_delete_with_wrong_credentials(
logger.info(f"Client registered: {client_info.client_id[:16]}...")
# Try to delete with wrong client_secret
logger.info("Attempting deletion with wrong client_secret...")
wrong_secret = "wrong_secret_" + secrets.token_urlsafe(32)
# Try to delete with wrong registration_access_token (RFC 7592 Bearer token)
logger.info("Attempting deletion with wrong registration_access_token...")
wrong_token = "wrong_token_" + secrets.token_urlsafe(32)
success = await delete_client(
nextcloud_url=nextcloud_host,
client_id=client_info.client_id,
client_secret=wrong_secret,
registration_access_token=wrong_token,
client_secret=client_info.client_secret, # Should not be used if token is present
)
assert not success, "Deletion with wrong credentials should fail"
logger.info("✅ Deletion correctly failed with wrong credentials")
# Clean up: Delete with correct credentials
logger.info("Cleaning up: deleting with correct credentials...")
# Clean up: Delete with correct RFC 7592 Bearer token
logger.info("Cleaning up: deleting with correct registration_access_token...")
success = await delete_client(
nextcloud_url=nextcloud_host,
client_id=client_info.client_id,
registration_access_token=client_info.registration_access_token,
client_secret=client_info.client_secret,
registration_client_uri=client_info.registration_client_uri,
)
assert success, "Deletion with correct credentials should succeed"
@@ -486,23 +444,27 @@ async def test_dcr_deletion_is_idempotent(
logger.info(f"Client registered: {client_info.client_id[:16]}...")
# First deletion
# First deletion with RFC 7592 Bearer token
logger.info("First deletion attempt...")
success = await delete_client(
nextcloud_url=nextcloud_host,
client_id=client_info.client_id,
registration_access_token=client_info.registration_access_token,
client_secret=client_info.client_secret,
registration_client_uri=client_info.registration_client_uri,
)
assert success, "First deletion should succeed"
logger.info("✅ First deletion succeeded")
# Second deletion (should fail gracefully)
# Second deletion (should fail gracefully - token no longer valid after first deletion)
logger.info("Second deletion attempt (should fail)...")
success = await delete_client(
nextcloud_url=nextcloud_host,
client_id=client_info.client_id,
registration_access_token=client_info.registration_access_token,
client_secret=client_info.client_secret,
registration_client_uri=client_info.registration_client_uri,
)
assert not success, "Second deletion should fail (client already deleted)"