mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 12:05:49 -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
@@ -17,6 +17,8 @@
|
|||||||
|
|
||||||
package org.keycloak.sdjwt.consumer;
|
package org.keycloak.sdjwt.consumer;
|
||||||
|
|
||||||
|
import java.net.URI;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@@ -158,11 +160,9 @@ public class JwtVcMetadataTrustedSdJwtIssuer implements TrustedSdJwtIssuer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private List<JWK> fetchIssuerMetadataJwks(String issuerUri) throws VerificationException {
|
private List<JWK> fetchIssuerMetadataJwks(String issuerUri) throws VerificationException {
|
||||||
// Build full URL to JWT VC metadata endpoint
|
// Build full URL to JWT VC metadata endpoint according to draft-ietf-oauth-sd-jwt-vc-13
|
||||||
|
String normalizedIssuerUri = normalizeUri(issuerUri);
|
||||||
issuerUri = normalizeUri(issuerUri);
|
String jwtVcIssuerUri = buildJwtVcIssuerMetadataUri(normalizedIssuerUri);
|
||||||
String jwtVcIssuerUri = issuerUri
|
|
||||||
.concat(JWT_VC_ISSUER_END_POINT); // Append well-known path
|
|
||||||
|
|
||||||
// Fetch and parse metadata
|
// Fetch and parse metadata
|
||||||
|
|
||||||
@@ -179,10 +179,10 @@ public class JwtVcMetadataTrustedSdJwtIssuer implements TrustedSdJwtIssuer {
|
|||||||
|
|
||||||
String exposedIssuerUri = normalizeUri(issuerMetadata.getIssuer());
|
String exposedIssuerUri = normalizeUri(issuerMetadata.getIssuer());
|
||||||
|
|
||||||
if (!issuerUri.equals(exposedIssuerUri)) {
|
if (!normalizedIssuerUri.equals(exposedIssuerUri)) {
|
||||||
throw new VerificationException(String.format(
|
throw new VerificationException(String.format(
|
||||||
"Unexpected metadata's issuer. Expected=%s, Got=%s",
|
"Unexpected metadata's issuer. Expected=%s, Got=%s",
|
||||||
issuerUri, exposedIssuerUri
|
normalizedIssuerUri, exposedIssuerUri
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,6 +211,26 @@ public class JwtVcMetadataTrustedSdJwtIssuer implements TrustedSdJwtIssuer {
|
|||||||
return Arrays.asList(jwks.getKeys());
|
return Arrays.asList(jwks.getKeys());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static String buildJwtVcIssuerMetadataUri(String issuerUri) throws VerificationException {
|
||||||
|
try {
|
||||||
|
URI parsedIssuer = URI.create(issuerUri);
|
||||||
|
String issuerPath = Optional.ofNullable(parsedIssuer.getRawPath()).orElse("");
|
||||||
|
String metadataPath = JWT_VC_ISSUER_END_POINT + issuerPath;
|
||||||
|
|
||||||
|
URI metadata = new URI(
|
||||||
|
parsedIssuer.getScheme(),
|
||||||
|
parsedIssuer.getAuthority(),
|
||||||
|
metadataPath,
|
||||||
|
null,
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
return metadata.toString();
|
||||||
|
} catch (IllegalArgumentException | URISyntaxException ex) {
|
||||||
|
throw new VerificationException("Invalid issuer URI", ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private JsonNode fetchData(String uri) throws VerificationException {
|
private JsonNode fetchData(String uri) throws VerificationException {
|
||||||
try {
|
try {
|
||||||
return Objects.requireNonNull(httpDataFetcher.fetchJsonData(uri));
|
return Objects.requireNonNull(httpDataFetcher.fetchJsonData(uri));
|
||||||
@@ -222,7 +242,7 @@ public class JwtVcMetadataTrustedSdJwtIssuer implements TrustedSdJwtIssuer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String normalizeUri(String uri) {
|
private static String normalizeUri(String uri) {
|
||||||
// Remove any trailing slash
|
// Remove any trailing slash
|
||||||
return uri.replaceAll("/$", "");
|
return uri.replaceAll("/$", "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import org.junit.ClassRule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import static org.keycloak.OID4VCConstants.CLAIM_NAME_ISSUER;
|
import static org.keycloak.OID4VCConstants.CLAIM_NAME_ISSUER;
|
||||||
import static org.keycloak.OID4VCConstants.JWT_VC_ISSUER_END_POINT;
|
|
||||||
|
|
||||||
import static org.hamcrest.CoreMatchers.endsWith;
|
import static org.hamcrest.CoreMatchers.endsWith;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
@@ -67,6 +66,24 @@ public abstract class JwtVcMetadataTrustedSdJwtIssuerTest {
|
|||||||
assertEquals(3, keys.size());
|
assertEquals(3, keys.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldResolveIssuerVerifyingKeysWithRealmPath() throws Exception {
|
||||||
|
String issuerUri = "https://issuer.example.com/realms/test-realm";
|
||||||
|
|
||||||
|
ObjectNode metadata = SdJwtUtils.mapper.createObjectNode();
|
||||||
|
metadata.put("issuer", issuerUri);
|
||||||
|
metadata.set("jwks", exampleJwks());
|
||||||
|
|
||||||
|
TrustedSdJwtIssuer trustedIssuer = new JwtVcMetadataTrustedSdJwtIssuer(
|
||||||
|
issuerUri, mockHttpDataFetcherWithMetadataAndJwks(issuerUri, metadata, exampleJwks()));
|
||||||
|
|
||||||
|
IssuerSignedJWT issuerSignedJWT = exampleIssuerSignedJwtWithIssuer(issuerUri);
|
||||||
|
List<SignatureVerifierContext> keys = trustedIssuer
|
||||||
|
.resolveIssuerVerifyingKeys(issuerSignedJWT);
|
||||||
|
|
||||||
|
assertEquals(3, keys.size());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldResolveKeys_WhenIssuerTrustedOnRegexPattern() throws Exception {
|
public void shouldResolveKeys_WhenIssuerTrustedOnRegexPattern() throws Exception {
|
||||||
Pattern issuerUriRegex = Pattern.compile("https://.*\\.example\\.com");
|
Pattern issuerUriRegex = Pattern.compile("https://.*\\.example\\.com");
|
||||||
@@ -405,12 +422,16 @@ public abstract class JwtVcMetadataTrustedSdJwtIssuerTest {
|
|||||||
private HttpDataFetcher mockHttpDataFetcherWithMetadataAndJwks(
|
private HttpDataFetcher mockHttpDataFetcherWithMetadataAndJwks(
|
||||||
String issuer, JsonNode metadata, JsonNode jwks
|
String issuer, JsonNode metadata, JsonNode jwks
|
||||||
) {
|
) {
|
||||||
return uri -> {
|
String normalizedIssuer = issuer.replaceAll("/$", "");
|
||||||
if (!uri.startsWith(issuer)) {
|
String metadataUri;
|
||||||
throw new UnknownHostException("Unavailable URI");
|
try {
|
||||||
}
|
metadataUri = JwtVcMetadataTrustedSdJwtIssuer.buildJwtVcIssuerMetadataUri(normalizedIssuer);
|
||||||
|
} catch (VerificationException e) {
|
||||||
|
throw new IllegalArgumentException(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (uri.endsWith(JWT_VC_ISSUER_END_POINT)) {
|
return uri -> {
|
||||||
|
if (uri.equals(metadataUri)) {
|
||||||
return metadata;
|
return metadata;
|
||||||
} else if (uri.endsWith("/api/vci/jwks")) {
|
} else if (uri.endsWith("/api/vci/jwks")) {
|
||||||
return jwks;
|
return jwks;
|
||||||
|
|||||||
@@ -18,15 +18,19 @@ package org.keycloak.protocol.oid4vc.issuance;
|
|||||||
|
|
||||||
import jakarta.ws.rs.core.UriInfo;
|
import jakarta.ws.rs.core.UriInfo;
|
||||||
|
|
||||||
|
import org.keycloak.http.HttpResponse;
|
||||||
import org.keycloak.jose.jwk.JSONWebKeySet;
|
import org.keycloak.jose.jwk.JSONWebKeySet;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.protocol.oid4vc.model.JWTVCIssuerMetadata;
|
import org.keycloak.protocol.oid4vc.model.JWTVCIssuerMetadata;
|
||||||
import org.keycloak.protocol.oidc.utils.JWKSServerUtils;
|
import org.keycloak.protocol.oidc.utils.JWKSServerUtils;
|
||||||
import org.keycloak.services.Urls;
|
import org.keycloak.services.Urls;
|
||||||
|
import org.keycloak.services.resources.ServerMetadataResource;
|
||||||
import org.keycloak.urls.UrlType;
|
import org.keycloak.urls.UrlType;
|
||||||
import org.keycloak.wellknown.WellKnownProvider;
|
import org.keycloak.wellknown.WellKnownProvider;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link WellKnownProvider} implementation for JWT VC Issuer metadata at endpoint /.well-known/jwt-vc-issuer
|
* {@link WellKnownProvider} implementation for JWT VC Issuer metadata at endpoint /.well-known/jwt-vc-issuer
|
||||||
* <p/>
|
* <p/>
|
||||||
@@ -35,6 +39,7 @@ import org.keycloak.wellknown.WellKnownProvider;
|
|||||||
* @author <a href="mailto:francis.pouatcha@adorsys.com">Francis Pouatcha</a>
|
* @author <a href="mailto:francis.pouatcha@adorsys.com">Francis Pouatcha</a>
|
||||||
*/
|
*/
|
||||||
public class JWTVCIssuerWellKnownProvider implements WellKnownProvider {
|
public class JWTVCIssuerWellKnownProvider implements WellKnownProvider {
|
||||||
|
private static final Logger LOGGER = Logger.getLogger(JWTVCIssuerWellKnownProvider.class);
|
||||||
private final KeycloakSession session;
|
private final KeycloakSession session;
|
||||||
|
|
||||||
public JWTVCIssuerWellKnownProvider(KeycloakSession session) {
|
public JWTVCIssuerWellKnownProvider(KeycloakSession session) {
|
||||||
@@ -51,6 +56,8 @@ public class JWTVCIssuerWellKnownProvider implements WellKnownProvider {
|
|||||||
UriInfo frontendUriInfo = session.getContext().getUri(UrlType.FRONTEND);
|
UriInfo frontendUriInfo = session.getContext().getUri(UrlType.FRONTEND);
|
||||||
RealmModel realm = session.getContext().getRealm();
|
RealmModel realm = session.getContext().getRealm();
|
||||||
|
|
||||||
|
addDeprecationHeadersIfOldRoute();
|
||||||
|
|
||||||
JWTVCIssuerMetadata config = new JWTVCIssuerMetadata();
|
JWTVCIssuerMetadata config = new JWTVCIssuerMetadata();
|
||||||
config.setIssuer(Urls.realmIssuer(frontendUriInfo.getBaseUri(), realm.getName()));
|
config.setIssuer(Urls.realmIssuer(frontendUriInfo.getBaseUri(), realm.getName()));
|
||||||
|
|
||||||
@@ -59,4 +66,39 @@ public class JWTVCIssuerWellKnownProvider implements WellKnownProvider {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach deprecation headers/log for the legacy realm-scoped route:
|
||||||
|
* old: /realms/{realm}/.well-known/jwt-vc-issuer
|
||||||
|
* new: /.well-known/jwt-vc-issuer/realms/{realm}
|
||||||
|
*/
|
||||||
|
private void addDeprecationHeadersIfOldRoute() {
|
||||||
|
String requestPath = session.getContext().getUri().getRequestUri().getPath();
|
||||||
|
if (requestPath == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int idxRealms = requestPath.indexOf("/realms/");
|
||||||
|
int idxWellKnown = requestPath.indexOf("/.well-known/");
|
||||||
|
boolean isOldRoute = idxRealms >= 0 && idxWellKnown > idxRealms;
|
||||||
|
if (!isOldRoute) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var realm = session.getContext().getRealm();
|
||||||
|
if (realm == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var base = session.getContext().getUri().getBaseUriBuilder();
|
||||||
|
var successor = ServerMetadataResource.wellKnownOAuthProviderUrl(base)
|
||||||
|
.build(JWTVCIssuerWellKnownProviderFactory.PROVIDER_ID, realm.getName());
|
||||||
|
|
||||||
|
HttpResponse httpResponse = session.getContext().getHttpResponse();
|
||||||
|
httpResponse.setHeader("Warning", "299 - \"Deprecated endpoint; use " + successor + "\"");
|
||||||
|
httpResponse.setHeader("Deprecation", "true");
|
||||||
|
httpResponse.setHeader("Link", "<" + successor + ">; rel=\"successor-version\"");
|
||||||
|
|
||||||
|
LOGGER.warnf("Deprecated realm-scoped well-known endpoint accessed for JWT VC issuer in realm '%s'. Use %s instead.", realm.getName(), successor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import jakarta.ws.rs.ext.Provider;
|
|||||||
|
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.protocol.oauth2.OAuth2WellKnownProviderFactory;
|
import org.keycloak.protocol.oauth2.OAuth2WellKnownProviderFactory;
|
||||||
|
import org.keycloak.protocol.oid4vc.issuance.JWTVCIssuerWellKnownProviderFactory;
|
||||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory;
|
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory;
|
||||||
import org.keycloak.services.cors.Cors;
|
import org.keycloak.services.cors.Cors;
|
||||||
|
|
||||||
@@ -73,6 +74,7 @@ public class ServerMetadataResource {
|
|||||||
// you can add codes here considering the current status of the implementation (preview, experimental).
|
// you can add codes here considering the current status of the implementation (preview, experimental).
|
||||||
if (OAuth2WellKnownProviderFactory.PROVIDER_ID.equals(providerName)) return true;
|
if (OAuth2WellKnownProviderFactory.PROVIDER_ID.equals(providerName)) return true;
|
||||||
if (OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID.equals(providerName)) return true;
|
if (OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID.equals(providerName)) return true;
|
||||||
|
if (JWTVCIssuerWellKnownProviderFactory.PROVIDER_ID.equals(providerName)) return true;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ import org.keycloak.models.ClientScopeModel;
|
|||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.UserSessionModel;
|
import org.keycloak.models.UserSessionModel;
|
||||||
import org.keycloak.models.oid4vci.CredentialScopeModel;
|
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.OID4VCAuthorizationDetailsResponse;
|
||||||
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
|
import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint;
|
||||||
import org.keycloak.protocol.oid4vc.issuance.TimeProvider;
|
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;
|
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) {
|
protected String getCredentialOfferUriUrl(String configId) {
|
||||||
return getCredentialOfferUriUrl(configId, true, "john");
|
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.CredentialResponseEncryptionMetadata;
|
||||||
import org.keycloak.protocol.oid4vc.model.DisplayObject;
|
import org.keycloak.protocol.oid4vc.model.DisplayObject;
|
||||||
import org.keycloak.protocol.oid4vc.model.Format;
|
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.KeyAttestationsRequired;
|
||||||
import org.keycloak.protocol.oid4vc.model.ProofTypesSupported;
|
import org.keycloak.protocol.oid4vc.model.ProofTypesSupported;
|
||||||
import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration;
|
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
|
@Test
|
||||||
public void testUnsignedMetadataWhenSignedDisabled() {
|
public void testUnsignedMetadataWhenSignedDisabled() {
|
||||||
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user