diff --git a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx index 56365a3f3b5..e9194da7eb6 100644 --- a/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx +++ b/js/apps/admin-ui/src/realm-settings/GeneralTab.tsx @@ -303,7 +303,7 @@ function RealmSettingsGeneralTabForm({ diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java index 00298e1ab5a..7d785672f79 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialsOffer.java @@ -17,13 +17,18 @@ package org.keycloak.protocol.oid4vc.model; +import java.beans.Transient; import java.util.Collections; import java.util.List; import java.util.Objects; +import org.keycloak.common.util.KeycloakUriBuilder; + import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import static org.keycloak.OID4VCConstants.WELL_KNOWN_OPENID_CREDENTIAL_ISSUER; + /** * Represents a CredentialsOffer according to the OID4VCI Spec * {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer} @@ -52,6 +57,23 @@ public class CredentialsOffer { return this; } + @Transient + public String getIssuerMetadataUrl() { + var metadataUrl = KeycloakUriBuilder + .fromUri(credentialIssuer) + .path("/.well-known/" + WELL_KNOWN_OPENID_CREDENTIAL_ISSUER); + var idx = credentialIssuer.indexOf("/realms"); + if (idx > 0) { + var baseUrl = credentialIssuer.substring(0, idx); + var realmPath = credentialIssuer.substring(idx); + metadataUrl = KeycloakUriBuilder + .fromUri(baseUrl) + .path("/.well-known/" + WELL_KNOWN_OPENID_CREDENTIAL_ISSUER) + .path(realmPath); + } + return metadataUrl.buildAsString(); + } + public List getCredentialConfigurationIds() { return credentialConfigurationIds; } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java index ee7dd839063..c0e49e769c8 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowTestBase.java @@ -96,7 +96,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn Oid4vcTestContext ctx = new Oid4vcTestContext(); // Get credential issuer metadata - HttpGet getCredentialIssuer = new HttpGet(getRealmPath(TEST_REALM_NAME) + "/.well-known/openid-credential-issuer"); + HttpGet getCredentialIssuer = new HttpGet(getRealmMetadataPath(TEST_REALM_NAME)); try (CloseableHttpResponse response = httpClient.execute(getCredentialIssuer)) { assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); String s = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java index c2463ef94de..8c964359d30 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationCodeFlowWithPARTest.java @@ -99,7 +99,7 @@ public class OID4VCAuthorizationCodeFlowWithPARTest extends OID4VCIssuerEndpoint Oid4vcTestContext ctx = new Oid4vcTestContext(); // Get credential issuer metadata - HttpGet getCredentialIssuer = new HttpGet(getRealmPath(TEST_REALM_NAME) + "/.well-known/openid-credential-issuer"); + HttpGet getCredentialIssuer = new HttpGet(getRealmMetadataPath(TEST_REALM_NAME)); try (CloseableHttpResponse response = httpClient.execute(getCredentialIssuer)) { assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); String s = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java index 31a12eecaa9..0ecc5e2e837 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCAuthorizationDetailsFlowTestBase.java @@ -121,7 +121,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue ctx.credentialsOffer = JsonSerialization.readValue(s, CredentialsOffer.class); } - HttpGet getIssuerMetadata = new HttpGet(ctx.credentialsOffer.getCredentialIssuer() + "/.well-known/openid-credential-issuer"); + HttpGet getIssuerMetadata = new HttpGet(ctx.credentialsOffer.getIssuerMetadataUrl()); try (CloseableHttpResponse response = httpClient.execute(getIssuerMetadata)) { assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); String s = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java index 5dfa9349b8b..e70365ab2fc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerEndpointTest.java @@ -68,7 +68,6 @@ import org.keycloak.models.UserSessionModel; import org.keycloak.models.oid4vci.CredentialScopeModel; import org.keycloak.protocol.oid4vc.issuance.OID4VCAuthorizationDetailsResponse; import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint; -import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory; import org.keycloak.protocol.oid4vc.issuance.TimeProvider; import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.CredentialBuilder; import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.JwtCredentialBuilder; @@ -92,12 +91,10 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.services.managers.AppAuthManager; import org.keycloak.services.managers.AuthenticationManager; -import org.keycloak.services.resources.RealmsResource; import org.keycloak.testsuite.Assert; import org.keycloak.testsuite.admin.ApiUtil; import org.keycloak.testsuite.runonserver.RunOnServerException; import org.keycloak.testsuite.util.AdminClientUtil; -import org.keycloak.testsuite.util.oauth.OAuthClient; import org.keycloak.util.JsonSerialization; import com.fasterxml.jackson.core.type.TypeReference; @@ -473,11 +470,8 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest { String testCredentialConfigurationId = clientScope.getAttributes().get(CredentialScopeModel.CONFIGURATION_ID); try (Client client = AdminClientUtil.createResteasyClient()) { - UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT); - URI oid4vciDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder) - .build(TEST_REALM_NAME, - OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID); - WebTarget oid4vciDiscoveryTarget = client.target(oid4vciDiscoveryUri); + String metadataUrl = getRealmMetadataPath(TEST_REALM_NAME); + WebTarget oid4vciDiscoveryTarget = client.target(metadataUrl); // 1. Get authoriZation code without scope specified by wallet // 2. Using the code to get accesstoken @@ -528,7 +522,13 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest { } protected String getRealmPath(String realm) { - return suiteContext.getAuthServerInfo().getContextRoot().toString() + "/auth/realms/" + realm; + return suiteContext.getAuthServerInfo().getContextRoot() + "/auth/realms/" + realm; + } + + protected String getRealmMetadataPath(String realm) { + var contextRoot = suiteContext.getAuthServerInfo().getContextRoot(); + // [TODO] This should be contextRoot/.well-known/openid-credential-issuer/auth/realms/... + return contextRoot + "/auth/.well-known/openid-credential-issuer/realms/" + realm; } protected void requestCredential(String token, @@ -558,7 +558,7 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest { } public CredentialIssuer getCredentialIssuerMetadata() { - final String endpoint = getRealmPath(TEST_REALM_NAME) + "/.well-known/openid-credential-issuer"; + final String endpoint = getRealmMetadataPath(TEST_REALM_NAME); HttpGet getMetadataRequest = new HttpGet(endpoint); try (CloseableHttpResponse metadataResponse = httpClient.execute(getMetadataRequest)) { assertEquals(HttpStatus.SC_OK, metadataResponse.getStatusLine().getStatusCode()); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java index b076159df8a..bab0b2f1f0b 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCIssuerWellKnownProviderTest.java @@ -19,7 +19,6 @@ package org.keycloak.testsuite.oid4vc.issuance.signing; import java.io.IOException; import java.io.Serializable; -import java.net.URI; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; @@ -31,7 +30,6 @@ import java.util.Optional; import jakarta.ws.rs.client.Client; import jakarta.ws.rs.client.WebTarget; import jakarta.ws.rs.core.Response; -import jakarta.ws.rs.core.UriBuilder; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.Time; @@ -52,7 +50,6 @@ import org.keycloak.protocol.ProtocolMapper; import org.keycloak.protocol.oid4vc.OID4VCLoginProtocolFactory; import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint; import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider; -import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProviderFactory; import org.keycloak.protocol.oid4vc.issuance.mappers.OID4VCMapper; import org.keycloak.protocol.oid4vc.model.Claim; import org.keycloak.protocol.oid4vc.model.ClaimDisplay; @@ -68,7 +65,6 @@ import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; import org.keycloak.representations.idm.ProtocolMapperRepresentation; import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.services.resources.RealmsResource; import org.keycloak.testsuite.arquillian.SuiteContext; import org.keycloak.testsuite.client.KeycloakTestingClient; import org.keycloak.testsuite.util.AdminClientUtil; @@ -133,7 +129,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest @Test public void testUnsignedMetadata() { try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer"; + String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME); String expectedIssuer = getRealmPath(TEST_REALM_NAME); // Configure realm for unsigned metadata @@ -173,7 +169,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest @Test public void testSignedMetadata() { try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer"; + String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME); String expectedIssuer = getRealmPath(TEST_REALM_NAME); // Configure realm for signed metadata @@ -249,7 +245,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest @Test public void testUnsignedMetadataWhenSignedDisabled() { try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer"; + String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME); String expectedIssuer = getRealmPath(TEST_REALM_NAME); // Disable signed metadata @@ -279,7 +275,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest @Test public void testSignedMetadataWithInvalidLifespan() { try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer"; + String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME); String expectedIssuer = getRealmPath(TEST_REALM_NAME); // Configure invalid lifespan @@ -309,7 +305,7 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest @Test public void testSignedMetadataWithInvalidAlgorithm() { try (CloseableHttpClient httpClient = HttpClientBuilder.create().build()) { - String wellKnownUri = OAuthClient.AUTH_SERVER_ROOT + "/realms/" + TEST_REALM_NAME + "/.well-known/openid-credential-issuer"; + String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME); String expectedIssuer = getRealmPath(TEST_REALM_NAME); // Configure invalid algorithm @@ -456,10 +452,8 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerEndpointTest @Test public void testIssuerMetadataIncludesEncryptionSupport() throws IOException { try (Client client = AdminClientUtil.createResteasyClient()) { - UriBuilder builder = UriBuilder.fromUri(OAuthClient.AUTH_SERVER_ROOT); - URI oid4vciDiscoveryUri = RealmsResource.wellKnownProviderUrl(builder) - .build(TEST_REALM_NAME, OID4VCIssuerWellKnownProviderFactory.PROVIDER_ID); - WebTarget oid4vciDiscoveryTarget = client.target(oid4vciDiscoveryUri); + String wellKnownUri = getRealmMetadataPath(TEST_REALM_NAME); + WebTarget oid4vciDiscoveryTarget = client.target(wellKnownUri); try (Response discoveryResponse = oid4vciDiscoveryTarget.request().get()) { CredentialIssuer oid4vciIssuerConfig = JsonSerialization.readValue( diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java index 6b38a41cb8a..f3a07c992d5 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCJWTIssuerEndpointTest.java @@ -451,7 +451,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest { CredentialsOffer credentialsOffer = JsonSerialization.readValue(s, CredentialsOffer.class); // 3. Get the issuer metadata - HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getCredentialIssuer() + "/.well-known/openid-credential-issuer"); + HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getIssuerMetadataUrl()); CloseableHttpResponse issuerMetadataResponse = httpClient.execute(getIssuerMetadata); assertEquals(HttpStatus.SC_OK, issuerMetadataResponse.getStatusLine().getStatusCode()); s = IOUtils.toString(issuerMetadataResponse.getEntity().getContent(), StandardCharsets.UTF_8); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java index d8e86f2db5a..119dbc0b7b9 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCSdJwtIssuingEndpointTest.java @@ -323,7 +323,7 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest { CredentialsOffer credentialsOffer = JsonSerialization.readValue(s, CredentialsOffer.class); // 3. Get the issuer metadata - HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getCredentialIssuer() + "/.well-known/openid-credential-issuer"); + HttpGet getIssuerMetadata = new HttpGet(credentialsOffer.getIssuerMetadataUrl()); CloseableHttpResponse issuerMetadataResponse = httpClient.execute(getIssuerMetadata); assertEquals(HttpStatus.SC_OK, issuerMetadataResponse.getStatusLine().getStatusCode()); s = IOUtils.toString(issuerMetadataResponse.getEntity().getContent(), StandardCharsets.UTF_8);