mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 20:15:46 -06:00
Add spec-compliant jwt vc issuer well-known endpoint
- expose /.well-known/jwt-vc-issuer/realms/{realm} and keep legacy route with deprecation headers
- build consumer metadata URL per draft-ietf-oauth-sd-jwt-vc-13 and add realm-path coverage
- add integration test for new path plus deprecation headers on legacy endpoint
Closes #44256
Signed-off-by: Awambeng Rodrick <awambengrodrick@gmail.com>
Signed-off-by: Awambeng <awambengrodrick@gmail.com>
This commit is contained in:
committed by
Marek Posolda
parent
741c0ad959
commit
a1bffa3ddc
@@ -70,6 +70,7 @@ import org.keycloak.models.ClientScopeModel;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.UserSessionModel;
|
||||
import org.keycloak.models.oid4vci.CredentialScopeModel;
|
||||
import org.keycloak.protocol.oid4vc.issuance.JWTVCIssuerWellKnownProviderFactory;
|
||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCAuthorizationDetailsResponse;
|
||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
|
||||
import org.keycloak.protocol.oid4vc.issuance.TimeProvider;
|
||||
@@ -561,6 +562,17 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest {
|
||||
return contextRoot + "/auth/.well-known/openid-credential-issuer/realms/" + realm;
|
||||
}
|
||||
|
||||
protected String getSpecCompliantRealmMetadataPath(String realm) {
|
||||
var contextRoot = suiteContext.getAuthServerInfo().getContextRoot();
|
||||
// [TODO] This should be contextRoot/.well-known/jwt-vc-issuer/auth/realms/...
|
||||
return contextRoot + "/auth/.well-known/" + JWTVCIssuerWellKnownProviderFactory.PROVIDER_ID + "/realms/" + realm;
|
||||
}
|
||||
|
||||
protected String getLegacyJwtVcRealmMetadataPath(String realm) {
|
||||
var contextRoot = suiteContext.getAuthServerInfo().getContextRoot();
|
||||
return contextRoot + "/auth/realms/" + realm + "/.well-known/" + JWTVCIssuerWellKnownProviderFactory.PROVIDER_ID;
|
||||
}
|
||||
|
||||
protected String getCredentialOfferUriUrl(String configId) {
|
||||
return getCredentialOfferUriUrl(configId, true, "john");
|
||||
}
|
||||
|
||||
@@ -59,6 +59,7 @@ import org.keycloak.protocol.oid4vc.model.CredentialRequestEncryptionMetadata;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponseEncryptionMetadata;
|
||||
import org.keycloak.protocol.oid4vc.model.DisplayObject;
|
||||
import org.keycloak.protocol.oid4vc.model.Format;
|
||||
import org.keycloak.protocol.oid4vc.model.JWTVCIssuerMetadata;
|
||||
import org.keycloak.protocol.oid4vc.model.KeyAttestationsRequired;
|
||||
import org.keycloak.protocol.oid4vc.model.ProofTypesSupported;
|
||||
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
|
||||
@@ -239,6 +240,57 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldServeJwtVcMetadataAtSpecCompliantEndpoint() {
|
||||
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
||||
String realm = TEST_REALM_NAME;
|
||||
String wellKnownUri = getSpecCompliantRealmMetadataPath(realm);
|
||||
String expectedIssuer = getRealmPath(realm);
|
||||
|
||||
HttpGet get = new HttpGet(wellKnownUri);
|
||||
get.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(get)) {
|
||||
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
|
||||
String json = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
|
||||
|
||||
JWTVCIssuerMetadata metadata = JsonSerialization.readValue(json, JWTVCIssuerMetadata.class);
|
||||
assertNotNull(metadata);
|
||||
assertEquals(expectedIssuer, metadata.getIssuer());
|
||||
assertNotNull("JWKS must be present", metadata.getJwks());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to process spec-compliant JWT VC issuer metadata response: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldKeepLegacyJwtVcEndpointWithDeprecationHeaders() {
|
||||
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
||||
String realm = TEST_REALM_NAME;
|
||||
String wellKnownUri = getLegacyJwtVcRealmMetadataPath(realm); // legacy JWT VC path
|
||||
|
||||
HttpGet get = new HttpGet(wellKnownUri);
|
||||
get.addHeader(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON);
|
||||
|
||||
try (CloseableHttpResponse response = httpClient.execute(get)) {
|
||||
assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
|
||||
Header warning = response.getFirstHeader("Warning");
|
||||
Header deprecation = response.getFirstHeader("Deprecation");
|
||||
Header link = response.getFirstHeader("Link");
|
||||
assertNotNull("Warning header should be present", warning);
|
||||
assertTrue("Warning header should mention deprecated endpoint", warning.getValue().contains("Deprecated endpoint"));
|
||||
assertNotNull("Deprecation header should be present", deprecation);
|
||||
assertEquals("true", deprecation.getValue());
|
||||
assertNotNull("Link header should point to successor", link);
|
||||
assertTrue("Link header should reference spec-compliant endpoint",
|
||||
link.getValue().contains(getSpecCompliantRealmMetadataPath(realm)));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to process legacy JWT VC issuer metadata response: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testUnsignedMetadataWhenSignedDisabled() {
|
||||
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
||||
|
||||
Reference in New Issue
Block a user