From bafad32805219bcd60e9af5da38342952fea39e3 Mon Sep 17 00:00:00 2001 From: Takashi Norimatsu Date: Fri, 17 Apr 2026 10:23:52 +0900 Subject: [PATCH] feat: check for CIMD of authorization server metadata --- src/index.ts | 3 +- src/runner/authorization-server.ts | 7 +- .../authorization-server-metadata.test.ts | 80 +++++++++++++++++++ .../authorization-server-metadata.ts | 42 +++++++++- src/types.ts | 5 +- 5 files changed, 129 insertions(+), 8 deletions(-) diff --git a/src/index.ts b/src/index.ts index 38f7701..5b6f5e2 100644 --- a/src/index.ts +++ b/src/index.ts @@ -495,7 +495,8 @@ program const result = await runAuthorizationServerConformanceTest( validated.url, scenarioName, - outputDir + outputDir, + specVersionFilter ); allResults.push({ scenario: scenarioName, checks: result.checks }); } catch (error) { diff --git a/src/runner/authorization-server.ts b/src/runner/authorization-server.ts index 5b4094b..1530bee 100644 --- a/src/runner/authorization-server.ts +++ b/src/runner/authorization-server.ts @@ -1,13 +1,14 @@ import { promises as fs } from 'fs'; import path from 'path'; -import { ConformanceCheck } from '../types'; +import { ConformanceCheck, SpecVersion } from '../types'; import { getClientScenarioForAuthorizationServer } from '../scenarios'; import { createResultDir } from './utils'; export async function runAuthorizationServerConformanceTest( serverUrl: string, scenarioName: string, - outputDir?: string + outputDir?: string, + specVersion?: SpecVersion ): Promise<{ checks: ConformanceCheck[]; resultDir?: string; @@ -31,7 +32,7 @@ export async function runAuthorizationServerConformanceTest( `Running client scenario for authorization server '${scenarioName}' against server: ${serverUrl}` ); - const checks = await scenario.run(serverUrl); + const checks = await scenario.run(serverUrl, specVersion); if (resultDir) { await fs.writeFile( diff --git a/src/scenarios/authorization-server/authorization-server-metadata.test.ts b/src/scenarios/authorization-server/authorization-server-metadata.test.ts index 1cf862b..4d39179 100644 --- a/src/scenarios/authorization-server/authorization-server-metadata.test.ts +++ b/src/scenarios/authorization-server/authorization-server-metadata.test.ts @@ -88,4 +88,84 @@ describe('AuthorizationServerMetadataEndpointScenario', () => { expect(check.status).toBe('FAILURE'); expect(check.errorMessage).toContain('code_challenge_methods_supported'); }); + + it('returns SUCCESS for CIMD check when server metadata includes client_id_metadata_document_supported=true with spec version 2025-11-25', async () => { + const scenario = new AuthorizationServerMetadataEndpointScenario(); + mockMetadataResponse({ + ...validMetadata, + client_id_metadata_document_supported: true + }); + + const checks = await scenario.run(SERVER_URL, '2025-11-25'); + + expect(checks).toHaveLength(2); + + const metadataCheck = checks[0]; + expect(metadataCheck.status).toBe('SUCCESS'); + + const cimdCheck = checks[1]; + expect(cimdCheck.id).toBe('authorization-server-metadata-cimd'); + expect(cimdCheck.status).toBe('SUCCESS'); + expect(cimdCheck.errorMessage).toBeUndefined(); + expect(cimdCheck.details).toEqual({ + client_id_metadata_document_supported: true + }); + }); + + it('returns FAILURE for CIMD check when server metadata lacks client_id_metadata_document_supported with spec version 2025-11-25', async () => { + const scenario = new AuthorizationServerMetadataEndpointScenario(); + mockMetadataResponse(validMetadata); + + const checks = await scenario.run(SERVER_URL, '2025-11-25'); + + expect(checks).toHaveLength(2); + + const metadataCheck = checks[0]; + expect(metadataCheck.status).toBe('SUCCESS'); + + const cimdCheck = checks[1]; + expect(cimdCheck.id).toBe('authorization-server-metadata-cimd'); + expect(cimdCheck.status).toBe('FAILURE'); + expect(cimdCheck.errorMessage).toContain( + 'client_id_metadata_document_supported' + ); + }); + + it('returns FAILURE for CIMD check when client_id_metadata_document_supported is false with spec version 2025-11-25', async () => { + const scenario = new AuthorizationServerMetadataEndpointScenario(); + mockMetadataResponse({ + ...validMetadata, + client_id_metadata_document_supported: false + }); + + const checks = await scenario.run(SERVER_URL, '2025-11-25'); + + expect(checks).toHaveLength(2); + + const metadataCheck = checks[0]; + expect(metadataCheck.status).toBe('SUCCESS'); + + const cimdCheck = checks[1]; + expect(cimdCheck.id).toBe('authorization-server-metadata-cimd'); + expect(cimdCheck.status).toBe('FAILURE'); + expect(cimdCheck.errorMessage).toContain( + 'client_id_metadata_document_supported' + ); + expect(cimdCheck.errorMessage).toContain('false'); + }); + + it('does not add CIMD check when spec version is 2025-06-18 even if claim is false', async () => { + const scenario = new AuthorizationServerMetadataEndpointScenario(); + mockMetadataResponse({ + ...validMetadata, + client_id_metadata_document_supported: false + }); + + const checks = await scenario.run(SERVER_URL, '2025-06-18'); + + expect(checks).toHaveLength(1); + + const metadataCheck = checks[0]; + expect(metadataCheck.status).toBe('SUCCESS'); + }); }); diff --git a/src/scenarios/authorization-server/authorization-server-metadata.ts b/src/scenarios/authorization-server/authorization-server-metadata.ts index 96b3669..9651c87 100644 --- a/src/scenarios/authorization-server/authorization-server-metadata.ts +++ b/src/scenarios/authorization-server/authorization-server-metadata.ts @@ -25,11 +25,15 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar - Return a JSON response including issuer, authorization_endpoint, token_endpoint and response_types_supported - The issuer value MUST match the URI obtained by removing the well-known URI string from the authorization server metadata URI.`; - async run(serverUrl: string): Promise { + async run( + serverUrl: string, + specVersion?: SpecVersion + ): Promise { let status: Status = 'SUCCESS'; let errorMessage: string | undefined; let details: any; let response: any | null = null; + let body: Record | undefined; try { const wellKnownUrls = this.createWellKnownUrl(serverUrl); @@ -53,7 +57,7 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar this.validateContentType(response.headers['content-type']); - const body = await this.parseJson(response); + body = await this.parseJson(response); const errors: string[] = []; this.validateMetadataBody(body, serverUrl, errors); @@ -71,7 +75,7 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar errorMessage = error instanceof Error ? error.message : String(error); } - return [ + const checks: ConformanceCheck[] = [ { id: 'authorization-server-metadata', name: 'AuthorizationServerMetadata', @@ -88,6 +92,38 @@ export class AuthorizationServerMetadataEndpointScenario implements ClientScenar ...(details ? { details } : {}) } ]; + + if (specVersion === '2025-11-25' && body) { + const cimdSupported = body.client_id_metadata_document_supported; + const cimdStatus: Status = cimdSupported === true ? 'SUCCESS' : 'FAILURE'; + const cimdErrorMessage = + cimdSupported === true + ? undefined + : cimdSupported === undefined + ? 'Authorization server metadata does not include "client_id_metadata_document_supported"' + : `Expected "client_id_metadata_document_supported" to be true, got ${JSON.stringify(cimdSupported)}`; + + checks.push({ + id: 'authorization-server-metadata-cimd', + name: 'AuthorizationServerMetadataCIMD', + description: + 'Authorization server metadata includes client_id_metadata_document_supported=true', + status: cimdStatus, + timestamp: new Date().toISOString(), + errorMessage: cimdErrorMessage, + specReferences: [ + { + id: 'IETF-OAuth-Client-ID-Metadata-Document', + url: 'https://www.ietf.org/archive/id/draft-ietf-oauth-client-id-metadata-document-01.html#name-authorization-server-metada' + } + ], + details: { + client_id_metadata_document_supported: cimdSupported + } + }); + } + + return checks; } private createWellKnownUrl(serverUrl: string): string[] { diff --git a/src/types.ts b/src/types.ts index 193686f..5ea275c 100644 --- a/src/types.ts +++ b/src/types.ts @@ -65,5 +65,8 @@ export interface ClientScenarioForAuthorizationServer { name: string; description: string; specVersions: SpecVersion[]; - run(serverUrl: string): Promise; + run( + serverUrl: string, + specVersion?: SpecVersion + ): Promise; }