diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/Endpoints.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/Endpoints.java index 65ba7697a80..426aacc452a 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/Endpoints.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/Endpoints.java @@ -90,8 +90,8 @@ public class Endpoints { return asString(getBase().path(RealmsResource.class).path("{realm}/protocol/oid4vc/nonce")); } - public String getOid4vcCredentialOffer(String nonce) { - return asString(getBase().path(RealmsResource.class).path("{realm}/protocol/oid4vc/credential-offer/").path(nonce)); + public String getOid4vcCredentialOffer() { + return asString(getBase().path(RealmsResource.class).path("{realm}/protocol/oid4vc/credential-offer")); } public String getOid4vcCredentialOfferUri() { diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferRequest.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferRequest.java index 7bdd0a4c575..a839c31b2fd 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferRequest.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferRequest.java @@ -1,7 +1,9 @@ package org.keycloak.testsuite.util.oauth.oid4vc; import java.io.IOException; +import java.util.Optional; +import org.keycloak.protocol.oid4vc.model.CredentialOfferURI; import org.keycloak.testsuite.util.oauth.AbstractHttpGetRequest; import org.keycloak.testsuite.util.oauth.AbstractOAuthClient; @@ -9,28 +11,25 @@ import org.apache.http.client.methods.CloseableHttpResponse; public class CredentialOfferRequest extends AbstractHttpGetRequest { - private String nonce; + private final CredentialOfferURI credOfferURI; - public CredentialOfferRequest(AbstractOAuthClient client) { + public CredentialOfferRequest(AbstractOAuthClient client, CredentialOfferURI credOfferUri) { super(client); + this.credOfferURI = credOfferUri; } - public CredentialOfferRequest(String nonce, AbstractOAuthClient client) { + public CredentialOfferRequest(AbstractOAuthClient client, String nonce) { super(client); - this.nonce = nonce; - } - - public CredentialOfferRequest nonce(String nonce) { - this.nonce = nonce; - return this; + credOfferURI = new CredentialOfferURI(); + credOfferURI.setIssuer(client.getEndpoints().getOid4vcCredentialOffer()); + credOfferURI.setNonce(nonce); } @Override protected String getEndpoint() { - if (nonce == null) { - throw new IllegalStateException("Nonce must be provided either via constructor, nonce() method, or endpoint must be overridden"); - } - return client.getEndpoints().getOid4vcCredentialOffer(nonce); + return Optional.ofNullable(credOfferURI) + .map(CredentialOfferURI::getCredentialOfferUri) + .orElseThrow(() -> new IllegalStateException("No credOfferURI")); } @Override diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferResponse.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferResponse.java index d1f294676a2..3218fc95def 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferResponse.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/CredentialOfferResponse.java @@ -1,6 +1,7 @@ package org.keycloak.testsuite.util.oauth.oid4vc; import java.io.IOException; +import java.util.Optional; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; import org.keycloak.testsuite.util.oauth.AbstractHttpResponse; @@ -21,6 +22,7 @@ public class CredentialOfferResponse extends AbstractHttpResponse { } public CredentialsOffer getCredentialsOffer() { - return credentialsOffer; + return Optional.ofNullable(credentialsOffer).orElseThrow(() -> + new IllegalStateException(String.format("[%s] %s", getError(), getErrorDescription()))); } } diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/OID4VCClient.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/OID4VCClient.java index 56155a92cf4..04d8dccb8e9 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/OID4VCClient.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/OID4VCClient.java @@ -1,5 +1,6 @@ package org.keycloak.testsuite.util.oauth.oid4vc; +import org.keycloak.protocol.oid4vc.model.CredentialOfferURI; import org.keycloak.testsuite.util.oauth.AbstractOAuthClient; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; @@ -23,16 +24,12 @@ public class OID4VCClient { return new CredentialOfferUriRequest(client, credConfigId); } - public CredentialOfferRequest credentialOfferRequest() { - return new CredentialOfferRequest(client); + public CredentialOfferRequest credentialOfferRequest(CredentialOfferURI credOfferUri) { + return new CredentialOfferRequest(client, credOfferUri); } public CredentialOfferRequest credentialOfferRequest(String nonce) { - return new CredentialOfferRequest(nonce, client); - } - - public CredentialOfferResponse doCredentialOfferRequest(String nonce) { - return credentialOfferRequest(nonce).send(); + return new CredentialOfferRequest(client, nonce); } public Oid4vcCredentialRequest credentialRequest() { @@ -50,8 +47,4 @@ public class OID4VCClient { public Oid4vcNonceRequest nonceRequest() { return new Oid4vcNonceRequest(client); } - - public String doNonceRequest() { - return nonceRequest().send().getNonce(); - } } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/OID4VCICredentialOfferMatrixTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/OID4VCICredentialOfferMatrixTest.java index f4525dad9b9..d65443ebbbc 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/OID4VCICredentialOfferMatrixTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/OID4VCICredentialOfferMatrixTest.java @@ -105,8 +105,8 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { String appClient; CredentialIssuer issuerMetadata; OIDCConfigurationRepresentation authorizationMetadata; - SupportedCredentialConfiguration supportedCredentialConfiguration; - + SupportedCredentialConfiguration credentialConfiguration; + TestContext(boolean preAuth, String appClient, String appUser) { this.preAuthorized = preAuth; this.issUser = issUsername; @@ -115,7 +115,7 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { this.appClient = appClient; this.issuerMetadata = getCredentialIssuerMetadata(); this.authorizationMetadata = getAuthorizationMetadata(this.issuerMetadata.getAuthorizationServers().get(0)); - this.supportedCredentialConfiguration = this.issuerMetadata.getCredentialsSupported().get(credConfigId); + this.credentialConfiguration = this.issuerMetadata.getCredentialsSupported().get(credConfigId); } } @@ -252,13 +252,12 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { // Exclude scope: // Require role: credential-offer-create verifyTokenJwt(ctx, issToken, - List.of(), List.of(ctx.supportedCredentialConfiguration.getScope()), + List.of(), List.of(ctx.credentialConfiguration.getScope()), List.of(CREDENTIAL_OFFER_CREATE.getName()), List.of()); // Retrieving the credential-offer-uri // CredentialOfferURI credOfferUri = getCredentialOfferUri(ctx, issToken); - String offerUri = credOfferUri.getCredentialOfferUri(); // Issuer logout in order to remove unwanted session state // @@ -268,7 +267,7 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { // Using the uri to get the actual credential offer // - CredentialsOffer credOffer = getCredentialsOffer(ctx, offerUri); + CredentialsOffer credOffer = getCredentialsOffer(ctx, credOfferUri); if (credOffer.getCredentialConfigurationIds().size() > 1) throw new IllegalStateException("Multiple credential configuration ids not supported in: " + JsonSerialization.valueAsString(credOffer)); @@ -397,7 +396,7 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { } private CredentialOfferURI getCredentialOfferUri(TestContext ctx, String token) throws Exception { - String credConfigId = ctx.supportedCredentialConfiguration.getId(); + String credConfigId = ctx.credentialConfiguration.getId(); CredentialOfferUriResponse credentialOfferURIResponse = oauth.oid4vc() .credentialOfferUriRequest(credConfigId) .preAuthorized(ctx.preAuthorized) @@ -411,19 +410,12 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { return credentialOfferURI; } - private CredentialsOffer getCredentialsOffer(TestContext ctx, String offerUri) throws Exception { + private CredentialsOffer getCredentialsOffer(TestContext ctx, CredentialOfferURI credOfferUri) throws Exception { CredentialOfferResponse credentialOfferResponse = oauth.oid4vc() - .credentialOfferRequest() - .endpoint(offerUri) + .credentialOfferRequest(credOfferUri) .send(); - int statusCode = credentialOfferResponse.getStatusCode(); - if (HttpStatus.SC_OK != statusCode) { - throw new IllegalStateException(credentialOfferResponse.getErrorDescription() != null - ? credentialOfferResponse.getErrorDescription() - : "Request failed with status " + statusCode); - } CredentialsOffer credOffer = credentialOfferResponse.getCredentialsOffer(); - assertEquals(List.of(ctx.supportedCredentialConfiguration.getId()), credOffer.getCredentialConfigurationIds()); + assertEquals(List.of(ctx.credentialConfiguration.getId()), credOffer.getCredentialConfigurationIds()); return credOffer; } @@ -479,7 +471,7 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { // No authorization_details, use credential_configuration_id credentialRequest.setCredentialConfigurationId(credConfigIds.get(0)); } - + return sendCredentialRequest(ctx, accessToken, credentialRequest); } @@ -512,7 +504,7 @@ public class OID4VCICredentialOfferMatrixTest extends OID4VCIssuerEndpointTest { private void verifyCredentialResponse(TestContext ctx, CredentialResponse credResponse) throws Exception { - String scope = ctx.supportedCredentialConfiguration.getScope(); + String scope = ctx.credentialConfiguration.getScope(); CredentialResponse.Credential credentialObj = credResponse.getCredentials().get(0); assertNotNull("The first credential in the array should not be null", credentialObj); 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 0481459b6b1..8a4bb0bd48b 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 @@ -138,8 +138,8 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue // Clear events before credential offer request events.clear(); - CredentialOfferResponse credentialOfferResponse = oauth.oid4vc().credentialOfferRequest() - .endpoint(credentialOfferURI.getIssuer() + "/" + credentialOfferURI.getNonce()) + CredentialOfferResponse credentialOfferResponse = oauth.oid4vc() + .credentialOfferRequest(credentialOfferURI) .send(); assertEquals(HttpStatus.SC_OK, credentialOfferResponse.getStatusCode()); ctx.credentialsOffer = credentialOfferResponse.getCredentialsOffer(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCCredentialOfferCorsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCCredentialOfferCorsTest.java index f5ddbb7278e..30f38679abb 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCCredentialOfferCorsTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCCredentialOfferCorsTest.java @@ -21,7 +21,6 @@ import java.io.IOException; import java.util.Arrays; import java.util.List; import java.util.Set; -import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import jakarta.ws.rs.core.HttpHeaders; @@ -83,7 +82,6 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { @Rule public TokenUtil tokenUtil = new TokenUtil(); - @Override public void configureTestRealm(RealmRepresentation testRealm) { super.configureTestRealm(testRealm); @@ -175,14 +173,14 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { public void testCredentialOfferSessionCorsValidOrigin() throws Exception { // First get a credential offer URI to obtain a nonce AccessTokenResponse tokenResponse = getAccessToken(); - String nonce = getNonceFromOfferUri(tokenResponse.getAccessToken()); + CredentialOfferURI credOfferUri = getCredentialOfferUri(tokenResponse.getAccessToken()); // Clear events before credential offer request events.clear(); // Test credential offer endpoint with valid origin CredentialOfferResponse response = oauth.oid4vc() - .credentialOfferRequest(nonce) + .credentialOfferRequest(credOfferUri) .header("Origin", VALID_CORS_URL) .send(); @@ -210,14 +208,11 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { public void testCredentialOfferSessionCorsInvalidOrigin() throws Exception { // First get a credential offer URI to obtain a nonce AccessTokenResponse tokenResponse = getAccessToken(); - String nonce = getNonceFromOfferUri(tokenResponse.getAccessToken()); + CredentialOfferURI credOfferUri = getCredentialOfferUri(tokenResponse.getAccessToken()); // Test credential offer endpoint with invalid origin - String offerUrl = getCredentialOfferUrl(nonce); - CredentialOfferResponse response = oauth.oid4vc() - .credentialOfferRequest() - .endpoint(offerUrl) + .credentialOfferRequest(credOfferUri) .header("Origin", INVALID_CORS_URL) .send(); @@ -230,10 +225,10 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { public void testCredentialOfferSessionCorsPreflightRequest() throws Exception { // First get a credential offer URI to obtain a nonce AccessTokenResponse tokenResponse = getAccessToken(); - String nonce = getNonceFromOfferUri(tokenResponse.getAccessToken()); + CredentialOfferURI credOfferUri = getCredentialOfferUri(tokenResponse.getAccessToken()); // Test preflight request for credential offer endpoint - String offerUrl = getCredentialOfferUrl(nonce); + String offerUrl = credOfferUri.getCredentialOfferUri(); try (CloseableHttpResponse response = makePreflightRequest(offerUrl, VALID_CORS_URL)) { assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode()); @@ -330,9 +325,8 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { // Create an expired credential offer using testing client // Use AtomicReference to avoid serialization issues with lambda captures - AtomicReference nonceHolder = new AtomicReference<>(); - final String issuerPath = getRealmPath(TEST_REALM_NAME); - testingClient.server(TEST_REALM_NAME).run(session -> { + String issuerPath = getRealmPath(TEST_REALM_NAME); + String nonce = testingClient.server(TEST_REALM_NAME).fetchString(session -> { CredentialsOffer credOffer = new CredentialsOffer() .setCredentialIssuer(issuerPath) .setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(new PreAuthorizedCode().setPreAuthorizedCode("test-code"))) @@ -344,18 +338,15 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { CredentialOfferState offerState = new CredentialOfferState(credOffer, null, null, Time.currentTime() - 1); offerStorage.putOfferState(session, offerState); session.getTransactionManager().commit(); - nonceHolder.set(offerState.getNonce()); + return offerState.getNonce(); }); - - String nonce = nonceHolder.get(); + assertNotNull("CredentialOffer nonce not null", nonce); events.clear(); // Try to fetch the expired credential offer - String offerUrl = getCredentialOfferUrl(nonce); CredentialOfferResponse response = oauth.oid4vc() - .credentialOfferRequest() - .endpoint(offerUrl) + .credentialOfferRequest(nonce) .header("Origin", VALID_CORS_URL) .send(); @@ -401,7 +392,8 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { return res; } - private String getNonceFromOfferUri(String accessToken) throws Exception { + private CredentialOfferURI getCredentialOfferUri(String accessToken) throws Exception { + CredentialOfferUriResponse response = oauth.oid4vc() .credentialOfferUriRequest(jwtTypeCredentialConfigurationIdName) .preAuthorized(true) @@ -411,8 +403,7 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { .send(); assertEquals(HttpStatus.SC_OK, response.getStatusCode()); - CredentialOfferURI offerUri = response.getCredentialOfferURI(); - return offerUri.getNonce(); + return response.getCredentialOfferURI(); } private CloseableHttpResponse makeCorsRequest(String url, String origin, String accessToken) throws IOException { 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 0d6b8ba8b06..c0bd9172f68 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 @@ -107,7 +107,6 @@ import org.keycloak.testsuite.runonserver.RunOnServerException; import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.oauth.OpenIDProviderConfigurationResponse; import org.keycloak.testsuite.util.oauth.oid4vc.CredentialIssuerMetadataResponse; -import org.keycloak.testsuite.util.oauth.oid4vc.Oid4vcCredentialResponse; import org.keycloak.userprofile.DeclarativeUserProfileProviderFactory; import org.keycloak.userprofile.config.UPConfigUtils; import org.keycloak.util.JsonSerialization; @@ -617,25 +616,6 @@ public abstract class OID4VCIssuerEndpointTest extends OID4VCTest { return getBasePath("test") + "credential-offer/" + nonce; } - protected void requestCredential(String token, - String credentialEndpoint, - SupportedCredentialConfiguration offeredCredential, - CredentialResponseHandler responseHandler, - ClientScopeRepresentation expectedClientScope) throws IOException, VerificationException { - Oid4vcCredentialResponse credentialRequestResponse = oauth.oid4vc() - .credentialRequest() - .endpoint(credentialEndpoint) - .bearerToken(token) - .credentialConfigurationId(offeredCredential.getId()) - .send(); - - assertEquals(HttpStatus.SC_OK, credentialRequestResponse.getStatusCode()); - CredentialResponse credentialResponse = credentialRequestResponse.getCredentialResponse(); - - // Use response handler to customize checks based on formats. - responseHandler.handleCredentialResponse(credentialResponse, expectedClientScope); - } - protected void requestCredentialWithIdentifier(String token, String credentialEndpoint, String credentialIdentifier, 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 7fc271a4376..7fdbc15bc77 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 @@ -463,7 +463,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest { // 2. Using the uri to get the actual credential offer CredentialsOffer credentialsOffer = oauth.oid4vc() - .credentialOfferRequest(credentialOfferURI.getNonce()) + .credentialOfferRequest(credentialOfferURI) .bearerToken(token) .send() .getCredentialsOffer(); 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 fa42902b2f9..23ebe7af02e 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 @@ -386,7 +386,7 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest { // 2. Using the uri to get the actual credential offer CredentialsOffer credentialsOffer = oauth.oid4vc() - .credentialOfferRequest(credentialOfferURI.getNonce()) + .credentialOfferRequest(credentialOfferURI) .bearerToken(token) .send() .getCredentialsOffer();