diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCAuthorizationDetailsProcessor.java b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCAuthorizationDetailsProcessor.java index e8f77452952..b217057444d 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCAuthorizationDetailsProcessor.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCAuthorizationDetailsProcessor.java @@ -40,10 +40,10 @@ import org.keycloak.protocol.oid4vc.model.Claim; import org.keycloak.protocol.oid4vc.model.ClaimsDescription; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration; import org.keycloak.protocol.oid4vc.utils.ClaimsPathPointer; import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantType; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; import org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessor; import org.keycloak.protocol.oidc.rar.InvalidAuthorizationDetailsException; import org.keycloak.representations.AuthorizationDetailsJSONRepresentation; @@ -113,7 +113,7 @@ public class OID4VCAuthorizationDetailsProcessor implements AuthorizationDetails // Skip if we're in pre-authorized code flow (it already has an offer state that will be updated) // Pre-authorized flow sets VC_ISSUANCE_FLOW note on the client session String vcIssuanceFlow = clientSession.getNote(PreAuthorizedCodeGrantType.VC_ISSUANCE_FLOW); - if (vcIssuanceFlow != null && vcIssuanceFlow.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) { + if (vcIssuanceFlow != null && vcIssuanceFlow.equals(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)) { logger.debugf("Skipping offer state creation for pre-authorized code flow (offer state already exists and will be updated)"); return; } diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java index 2087000b2e2..86549e91bf6 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/OID4VCIssuerEndpoint.java @@ -103,14 +103,12 @@ import org.keycloak.protocol.oid4vc.model.JwtProof; import org.keycloak.protocol.oid4vc.model.NonceResponse; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; import org.keycloak.protocol.oid4vc.model.OfferResponseType; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oid4vc.model.Proofs; import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration; import org.keycloak.protocol.oid4vc.model.VerifiableCredential; import org.keycloak.protocol.oid4vc.utils.ClaimsPathPointer; import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantType; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; import org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessor; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AuthorizationDetailsJSONRepresentation; @@ -534,8 +532,7 @@ public class OID4VCIssuerEndpoint { if (preAuthorized) { String code = "urn:oid4vci:code:" + SecretGenerator.getInstance().randomString(64); - credOffer.setGrants(new PreAuthorizedGrant().setPreAuthorizedCode( - new PreAuthorizedCode().setPreAuthorizedCode(code))); + credOffer.addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode(code)); } // Create the CredentialOfferState @@ -695,7 +692,7 @@ public class OID4VCIssuerEndpoint { AuthenticatedClientSessionModel clientSession = getAuthenticatedClientSession(); String vcIssuanceFlow = clientSession.getNote(PreAuthorizedCodeGrantType.VC_ISSUANCE_FLOW); - if (vcIssuanceFlow == null || !vcIssuanceFlow.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) { + if (vcIssuanceFlow == null || !vcIssuanceFlow.equals(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)) { // Use getAuthResult() instead of bearerTokenAuthenticator.authenticate() directly // This ensures we benefit from the cachedAuthResult caching that prevents DPoP proof reuse AccessToken accessToken = getAuthResult().token(); diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/credentialoffer/CredentialOfferState.java b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/credentialoffer/CredentialOfferState.java index 919357424de..7fb76810cc1 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/credentialoffer/CredentialOfferState.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/issuance/credentialoffer/CredentialOfferState.java @@ -24,8 +24,6 @@ import org.keycloak.common.util.Base64Url; import org.keycloak.common.util.Time; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant; import org.keycloak.saml.RandomSecret; import com.fasterxml.jackson.annotation.JsonInclude; @@ -55,9 +53,7 @@ public class CredentialOfferState { @Transient public Optional getPreAuthorizedCode() { - return Optional.ofNullable(credentialsOffer.getGrants()) - .map(PreAuthorizedGrant::getPreAuthorizedCode) - .map(PreAuthorizedCode::getPreAuthorizedCode); + return Optional.ofNullable(credentialsOffer.getPreAuthorizedCode()); } public void generateTxCode() { diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedGrant.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/AuthorizationCodeGrant.java similarity index 53% rename from services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedGrant.java rename to services/src/main/java/org/keycloak/protocol/oid4vc/model/AuthorizationCodeGrant.java index b7c727ec01e..6eed8c4c0af 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedGrant.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/AuthorizationCodeGrant.java @@ -1,5 +1,5 @@ /* - * Copyright 2024 Red Hat, Inc. and/or its affiliates + * Copyright 2026 Red Hat, Inc. and/or its affiliates * and other contributors as indicated by the @author tags. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -19,42 +19,50 @@ package org.keycloak.protocol.oid4vc.model; import java.util.Objects; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; - +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** - * Container for the pre-authorized code to be used in a Credential Offer + * Container for the authorization code grant to be used in a Credential Offer *

* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer} * - * @author Stefan Wiedemann + * @author Thomas Diesler */ @JsonInclude(JsonInclude.Include.NON_NULL) -public class PreAuthorizedGrant { +public class AuthorizationCodeGrant implements CredentialOfferGrant { - @JsonProperty(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE) - private PreAuthorizedCode preAuthorizedCode; + public static final String AUTH_CODE_GRANT_TYPE = "authorization_code"; + public static final String ISSUER_STATE = "issuer_state"; - public PreAuthorizedCode getPreAuthorizedCode() { - return preAuthorizedCode; + @Override + @JsonIgnore + public String getGrantType() { + return AUTH_CODE_GRANT_TYPE; } - public PreAuthorizedGrant setPreAuthorizedCode(PreAuthorizedCode preAuthorizedCode) { - this.preAuthorizedCode = preAuthorizedCode; + @JsonProperty(ISSUER_STATE) + private String issuerState; + + public String getIssuerState() { + return issuerState; + } + + public AuthorizationCodeGrant setIssuerState(String issuerState) { + this.issuerState = issuerState; return this; } @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof PreAuthorizedGrant grant)) return false; - return Objects.equals(getPreAuthorizedCode(), grant.getPreAuthorizedCode()); + if (!(o instanceof AuthorizationCodeGrant grant)) return false; + return Objects.equals(issuerState, grant.issuerState); } @Override public int hashCode() { - return Objects.hash(getPreAuthorizedCode()); + return Objects.hash(issuerState); } } diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialOfferGrant.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialOfferGrant.java new file mode 100644 index 00000000000..9dc5b0a4f90 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialOfferGrant.java @@ -0,0 +1,34 @@ +/* + * Copyright 2026 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oid4vc.model; + + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * Container for the grants to be used in a Credential Offer + *

+ * {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer} + * + * @author Thomas Diesler + */ +public interface CredentialOfferGrant { + + @JsonIgnore + String getGrantType(); +} diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialOfferGrantsDeserializer.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialOfferGrantsDeserializer.java new file mode 100644 index 00000000000..56fd465736b --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/CredentialOfferGrantsDeserializer.java @@ -0,0 +1,43 @@ +package org.keycloak.protocol.oid4vc.model; + +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.InvalidFormatException; + +import static org.keycloak.protocol.oid4vc.model.AuthorizationCodeGrant.AUTH_CODE_GRANT_TYPE; +import static org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE; + +public final class CredentialOfferGrantsDeserializer extends JsonDeserializer> { + + @Override + public Map deserialize(JsonParser p, DeserializationContext ctx) throws IOException { + + Map grants = new LinkedHashMap<>(); + var mapper = (ObjectMapper) p.getCodec(); + var node = mapper.readTree(p); + + var fields = node.fieldNames(); + while (fields.hasNext()) { + var grantType = fields.next(); + var valueNode = node.get(grantType); + + Class target = + switch (grantType) { + case AUTH_CODE_GRANT_TYPE -> AuthorizationCodeGrant.class; + case PRE_AUTH_GRANT_TYPE -> PreAuthorizedCodeGrant.class; + default -> throw new InvalidFormatException( + p, "Unknown grant type key: " + grantType, grantType, CredentialOfferGrant.class); + }; + + CredentialOfferGrant grant = mapper.treeToValue(valueNode, target); + grants.put(grantType, grant); + } + return grants; + } +} 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 7d785672f79..16759d7ff86 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,17 +17,24 @@ package org.keycloak.protocol.oid4vc.model; -import java.beans.Transient; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Optional; import org.keycloak.common.util.KeycloakUriBuilder; +import org.keycloak.util.JsonSerialization; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import static org.keycloak.OID4VCConstants.WELL_KNOWN_OPENID_CREDENTIAL_ISSUER; +import static org.keycloak.protocol.oid4vc.model.AuthorizationCodeGrant.AUTH_CODE_GRANT_TYPE; +import static org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE; /** * Represents a CredentialsOffer according to the OID4VCI Spec @@ -45,8 +52,9 @@ public class CredentialsOffer { @JsonProperty("credential_configuration_ids") private List credentialConfigurationIds; - // current implementation only supports pre-authorized codes. - private PreAuthorizedGrant grants; + @JsonProperty("grants") + @JsonDeserialize(using = CredentialOfferGrantsDeserializer.class) + private Map grants = new HashMap<>(); public String getCredentialIssuer() { return credentialIssuer; @@ -57,7 +65,7 @@ public class CredentialsOffer { return this; } - @Transient + @JsonIgnore public String getIssuerMetadataUrl() { var metadataUrl = KeycloakUriBuilder .fromUri(credentialIssuer) @@ -83,24 +91,55 @@ public class CredentialsOffer { return this; } - public PreAuthorizedGrant getGrants() { - return grants; + public CredentialOfferGrant getGrant(String grantType) { + return grants.get(grantType); } - public CredentialsOffer setGrants(PreAuthorizedGrant grants) { - this.grants = grants; + public CredentialsOffer addGrant(CredentialOfferGrant grant) { + grants.put(grant.getGrantType(), grant); return this; } + @JsonIgnore + public AuthorizationCodeGrant getAuthorizationCodeGrant() { + return (AuthorizationCodeGrant) grants.get(AUTH_CODE_GRANT_TYPE); + } + + @JsonIgnore + public String getIssuerState() { + return Optional.ofNullable(getAuthorizationCodeGrant()) + .map(AuthorizationCodeGrant::getIssuerState) + .orElse(null); + } + + @JsonIgnore + public PreAuthorizedCodeGrant getPreAuthorizedGrant() { + return (PreAuthorizedCodeGrant) grants.get(PRE_AUTH_GRANT_TYPE); + } + + @JsonIgnore + public String getPreAuthorizedCode() { + return Optional.ofNullable(getPreAuthorizedGrant()) + .map(PreAuthorizedCodeGrant::getPreAuthorizedCode) + .orElse(null); + } + @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof CredentialsOffer that)) return false; - return Objects.equals(getCredentialIssuer(), that.getCredentialIssuer()) && Objects.equals(getCredentialConfigurationIds(), that.getCredentialConfigurationIds()) && Objects.equals(getGrants(), that.getGrants()); + boolean match = Objects.equals(credentialIssuer, that.credentialIssuer); + match &= Objects.equals(credentialConfigurationIds, that.credentialConfigurationIds); + match &= Objects.equals(grants, that.grants); + return match; } @Override public int hashCode() { - return Objects.hash(getCredentialIssuer(), getCredentialConfigurationIds(), getGrants()); + return Objects.hash(credentialIssuer, credentialConfigurationIds, grants); + } + + public String toString() { + return JsonSerialization.valueAsString(this); } } diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/IssuerState.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/IssuerState.java new file mode 100644 index 00000000000..e5a880c4a65 --- /dev/null +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/IssuerState.java @@ -0,0 +1,73 @@ +/* + * Copyright 2026 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.protocol.oid4vc.model; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Objects; + +import org.keycloak.util.JsonSerialization; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Container for the issuer state used by the authorization code grant in a Credential Offer + *

+ * {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer} + * + * @author Thomas Diesler + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +public class IssuerState { + + @JsonProperty("credential_offer_id") + private String credentialOfferId; + + public String getCredentialOfferId() { + return credentialOfferId; + } + + public IssuerState setCredentialOfferId(String credentialOfferId) { + this.credentialOfferId = credentialOfferId; + return this; + } + + public static IssuerState fromEncodedString(String encoded) { + byte[] encodedBytes = encoded.getBytes(StandardCharsets.UTF_8); + String value = new String(Base64.getUrlDecoder().decode(encodedBytes)); + return JsonSerialization.valueFromString(value, IssuerState.class); + } + + public String encodeToString() { + String value = JsonSerialization.valueAsString(this); + return Base64.getUrlEncoder().encodeToString(value.getBytes(StandardCharsets.UTF_8)); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof IssuerState grant)) return false; + return Objects.equals(credentialOfferId, grant.credentialOfferId); + } + + @Override + public int hashCode() { + return Objects.hash(credentialOfferId); + } +} diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedCode.java b/services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedCodeGrant.java similarity index 57% rename from services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedCode.java rename to services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedCodeGrant.java index 97f38d7fee1..78210a4c45f 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedCode.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/model/PreAuthorizedCodeGrant.java @@ -19,32 +19,45 @@ package org.keycloak.protocol.oid4vc.model; import java.util.Objects; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** - * Represents a pre-authorized grant, as used by the Credential Offer in OID4VCI + * Container for the pre-authorized code to be used in a Credential Offer + *

* {@see https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-credential-offer} * * @author Stefan Wiedemann */ @JsonInclude(JsonInclude.Include.NON_NULL) -public class PreAuthorizedCode { +public class PreAuthorizedCodeGrant implements CredentialOfferGrant { - @JsonProperty("pre-authorized_code") + public static final String PRE_AUTH_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:pre-authorized_code"; + public static final String AUTHORIZATION_SERVER_PARAM = "authorization_server"; + public static final String CODE_REQUEST_PARAM = "pre-authorized_code"; + public static final String TX_CODE_PARAM = "tx_code"; + + @Override + @JsonIgnore + public String getGrantType() { + return PRE_AUTH_GRANT_TYPE; + } + + @JsonProperty(CODE_REQUEST_PARAM) private String preAuthorizedCode; - @JsonProperty("tx_code") + @JsonProperty(TX_CODE_PARAM) private TxCode txCode; - @JsonProperty("authorization_server") + @JsonProperty(AUTHORIZATION_SERVER_PARAM) private String authorizationServer; public String getPreAuthorizedCode() { return preAuthorizedCode; } - public PreAuthorizedCode setPreAuthorizedCode(String preAuthorizedCode) { + public PreAuthorizedCodeGrant setPreAuthorizedCode(String preAuthorizedCode) { this.preAuthorizedCode = preAuthorizedCode; return this; } @@ -53,7 +66,7 @@ public class PreAuthorizedCode { return txCode; } - public PreAuthorizedCode setTxCode(TxCode txCode) { + public PreAuthorizedCodeGrant setTxCode(TxCode txCode) { this.txCode = txCode; return this; } @@ -62,7 +75,7 @@ public class PreAuthorizedCode { return authorizationServer; } - public PreAuthorizedCode setAuthorizationServer(String authorizationServer) { + public PreAuthorizedCodeGrant setAuthorizationServer(String authorizationServer) { this.authorizationServer = authorizationServer; return this; } @@ -70,12 +83,13 @@ public class PreAuthorizedCode { @Override public boolean equals(Object o) { if (this == o) return true; - if (!(o instanceof PreAuthorizedCode that)) return false; - return Objects.equals(getPreAuthorizedCode(), that.getPreAuthorizedCode()) && Objects.equals(getTxCode(), that.getTxCode()) && Objects.equals(getAuthorizationServer(), that.getAuthorizationServer()); + if (!(o instanceof PreAuthorizedCodeGrant that)) return false; + return Objects.equals(preAuthorizedCode, that.preAuthorizedCode) + && Objects.equals(txCode, that.txCode) && Objects.equals(authorizationServer, that.authorizationServer); } @Override public int hashCode() { - return Objects.hash(getPreAuthorizedCode(), getTxCode(), getAuthorizationServer()); + return Objects.hash(preAuthorizedCode, txCode, authorizationServer); } } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java index ef2cc21baf6..00a328fce1c 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/endpoints/TokenEndpoint.java @@ -43,11 +43,11 @@ import org.keycloak.models.AuthenticatedClientSessionModel; import org.keycloak.models.ClientModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oidc.OIDCAdvancedConfigWrapper; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.protocol.oidc.grants.OAuth2GrantType; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; import org.keycloak.protocol.oidc.utils.AuthorizeClientUtil; import org.keycloak.protocol.saml.JaxrsSAML2BindingBuilder; import org.keycloak.protocol.saml.SamlClient; @@ -132,7 +132,7 @@ public class TokenEndpoint { if (!grantType.equals(OAuth2Constants.UMA_GRANT_TYPE) // pre-authorized grants are not necessarily used by known clients. - && !grantType.equals(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)) { + && !grantType.equals(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)) { checkClient(); checkParameterDuplicated(); } diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantType.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantType.java index 5984ee925a4..8bd355b439a 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantType.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantType.java @@ -42,6 +42,7 @@ import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint; import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerWellKnownProvider; import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferStorage; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oid4vc.utils.OID4VCUtil; import org.keycloak.protocol.oidc.OIDCLoginProtocol; import org.keycloak.protocol.oidc.TokenManager; @@ -80,12 +81,12 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase { } // See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-request - String preAuthCode = formParams.getFirst(PreAuthorizedCodeGrantTypeFactory.CODE_REQUEST_PARAM); - String txCode = formParams.getFirst(PreAuthorizedCodeGrantTypeFactory.TX_CODE_PARAM); + String preAuthCode = formParams.getFirst(PreAuthorizedCodeGrant.CODE_REQUEST_PARAM); + String txCode = formParams.getFirst(PreAuthorizedCodeGrant.TX_CODE_PARAM); if (preAuthCode == null) { // See: https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html#name-token-request - String errorMessage = "Missing parameter: " + PreAuthorizedCodeGrantTypeFactory.CODE_REQUEST_PARAM; + String errorMessage = "Missing parameter: " + PreAuthorizedCodeGrant.CODE_REQUEST_PARAM; event.detail(REASON, errorMessage).error(Errors.INVALID_CODE); throw new CorsErrorResponseException(cors, OAuthErrorException.INVALID_REQUEST, errorMessage, Response.Status.BAD_REQUEST); @@ -153,7 +154,7 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase { AuthenticatedClientSessionModel clientSession = session.sessions().createClientSession(realm, clientModel, userSession); clientSession.setNote(OIDCLoginProtocol.ISSUER, credOffer.getCredentialIssuer()); - clientSession.setNote(VC_ISSUANCE_FLOW, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE); + clientSession.setNote(VC_ISSUANCE_FLOW, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE); List credentialScopes = resolveCredentialScopesForCredentialOffer(credOffer.getCredentialConfigurationIds()); Set requestedScopes = TokenManager.getRequestedClientScopes(session, OAuth2Constants.SCOPE_OPENID, clientModel, targetUserModel) @@ -161,7 +162,7 @@ public class PreAuthorizedCodeGrantType extends OAuth2GrantTypeBase { requestedScopes.addAll(credentialScopes); ClientSessionContext sessionContext = fromClientSessionAndClientScopes(clientSession, requestedScopes, null, session); - sessionContext.setAttribute(Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE); + sessionContext.setAttribute(Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE); // set the client as retrieved from the pre-authorized session session.getContext().setClient(clientModel); diff --git a/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantTypeFactory.java b/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantTypeFactory.java index 398b5ae3476..a08b074a6ce 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantTypeFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/grants/PreAuthorizedCodeGrantTypeFactory.java @@ -23,6 +23,8 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.provider.EnvironmentDependentProviderFactory; +import static org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE; + /** * Factory for Pre-Authorized Code Grant * @@ -30,10 +32,6 @@ import org.keycloak.provider.EnvironmentDependentProviderFactory; */ public class PreAuthorizedCodeGrantTypeFactory implements OAuth2GrantTypeFactory, EnvironmentDependentProviderFactory { - public static final String GRANT_TYPE = "urn:ietf:params:oauth:grant-type:pre-authorized_code"; - public static final String CODE_REQUEST_PARAM = "pre-authorized_code"; - public static final String TX_CODE_PARAM = "tx_code"; - @Override public OAuth2GrantType create(KeycloakSession session) { return new PreAuthorizedCodeGrantType(); @@ -53,7 +51,7 @@ public class PreAuthorizedCodeGrantTypeFactory implements OAuth2GrantTypeFactory @Override public String getId() { - return GRANT_TYPE; + return PRE_AUTH_GRANT_TYPE; } @Override diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java index 269c7eb6dfb..83ded7af11b 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCBasicWallet.java @@ -105,7 +105,7 @@ public class OID4VCBasicWallet { CredentialsOffer credOffer = getCredentialOffer(ctx, credOfferUri) .send().getCredentialsOffer(); - String preAuthCode = credOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode(); + String preAuthCode = credOffer.getPreAuthorizedCode(); assertNotNull(preAuthCode, "No PreAuth Code"); return credOffer; diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCCredentialOfferMatrixTest.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCCredentialOfferMatrixTest.java index a923b05397a..5d811b00ae6 100644 --- a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCCredentialOfferMatrixTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCCredentialOfferMatrixTest.java @@ -166,7 +166,7 @@ public class OID4VCCredentialOfferMatrixTest extends OID4VCIssuerTestBase { // Create Pre-Authorized CredentialOffer // CredentialsOffer credOffer = wallet.createPreAuthCredentialOffer(ctx, null); - String preAuthCode = credOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode(); + String preAuthCode = credOffer.getPreAuthorizedCode(); // Redeem Pre-Authorized Code for AccessToken // @@ -195,7 +195,7 @@ public class OID4VCCredentialOfferMatrixTest extends OID4VCIssuerTestBase { // Create Pre-Authorized CredentialOffer // CredentialsOffer credOffer = wallet.createPreAuthCredentialOffer(ctx, ctx.holder); - String preAuthCode = credOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode(); + String preAuthCode = credOffer.getPreAuthorizedCode(); // Redeem Pre-Authorized Code for AccessToken // diff --git a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/PreAuthorizedCodeGrantRequest.java b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/PreAuthorizedCodeGrantRequest.java index 606a4ba0b54..d1d789e0a72 100644 --- a/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/PreAuthorizedCodeGrantRequest.java +++ b/tests/utils-shared/src/main/java/org/keycloak/testsuite/util/oauth/oid4vc/PreAuthorizedCodeGrantRequest.java @@ -5,7 +5,7 @@ import java.util.List; import org.keycloak.OAuth2Constants; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.testsuite.util.oauth.AbstractHttpPostRequest; import org.keycloak.testsuite.util.oauth.AbstractOAuthClient; import org.keycloak.testsuite.util.oauth.AccessTokenResponse; @@ -34,8 +34,8 @@ public class PreAuthorizedCodeGrantRequest extends AbstractHttpPostRequest parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8); post.setEntity(formEntity); @@ -127,7 +127,7 @@ public class PreAuthorizedGrantTest extends AbstractTestRealmKeycloakTest { private AccessTokenResponse postCode(String preAuthorizedCode) throws Exception { HttpPost post = new HttpPost(getTokenEndpoint()); List parameters = new LinkedList<>(); - parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)); + parameters.add(new BasicNameValuePair(OAuth2Constants.GRANT_TYPE, PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)); parameters.add(new BasicNameValuePair("pre-authorized_code", preAuthorizedCode)); UrlEncodedFormEntity formEntity = new UrlEncodedFormEntity(parameters, StandardCharsets.UTF_8); post.setEntity(formEntity); 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 23ab509a45d..7c748f1f973 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 @@ -36,7 +36,6 @@ import org.keycloak.protocol.oid4vc.model.CredentialResponse; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; import org.keycloak.protocol.oid4vc.model.ErrorType; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; import org.keycloak.representations.AccessToken; import org.keycloak.representations.idm.ClientRepresentation; @@ -304,7 +303,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -365,7 +364,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -415,7 +414,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -446,7 +445,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -477,7 +476,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -510,7 +509,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -541,7 +540,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); @@ -560,7 +559,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue // Send empty authorization_details array - should fail AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(List.of()) .send(); @@ -579,7 +578,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue // The system should generate authorization_details based on credential_configuration_ids from the credential offer AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .send(); @@ -617,11 +616,10 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue String token = getBearerToken(oauth, client, getCredentialClientScope().getName()); Oid4vcTestContext ctx = prepareOid4vcTestContext(token); - PreAuthorizedCode preAuthorizedCode = ctx.credentialsOffer.getGrants().getPreAuthorizedCode(); // Step 1: Request token without authorization_details parameter (no scope needed) AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(preAuthorizedCode.getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .send(); @@ -700,11 +698,10 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue public void testPreAuthorizedCodeTokenEndpointRestriction() throws Exception { String token = getBearerToken(oauth, client, getCredentialClientScope().getName()); Oid4vcTestContext ctx = prepareOid4vcTestContext(token); - PreAuthorizedCode preAuthorizedCode = ctx.credentialsOffer.getGrants().getPreAuthorizedCode(); // Step 1: Get pre-authorized code token AccessTokenResponse accessTokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(preAuthorizedCode.getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .send(); @@ -801,7 +798,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue // Step 1: Request token without authorization_details parameter AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .send(); @@ -875,7 +872,7 @@ public abstract class OID4VCAuthorizationDetailsFlowTestBase extends OID4VCIssue List authDetails = List.of(authDetail); AccessTokenResponse tokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(ctx.credentialsOffer.getPreAuthorizedCode()) .endpoint(ctx.openidConfig.getTokenEndpoint()) .authorizationDetails(authDetails) .send(); 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 bfee5d7939a..2d040e60953 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 @@ -35,8 +35,7 @@ import org.keycloak.protocol.oid4vc.issuance.credentialoffer.CredentialOfferStor import org.keycloak.protocol.oid4vc.model.CredentialOfferURI; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; import org.keycloak.protocol.oid4vc.model.ErrorType; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.cors.Cors; import org.keycloak.testsuite.AssertEvents; @@ -327,7 +326,7 @@ public class OID4VCCredentialOfferCorsTest extends OID4VCIssuerEndpointTest { String nonce = testingClient.server(TEST_REALM_NAME).fetchString(session -> { CredentialsOffer credOffer = new CredentialsOffer() .setCredentialIssuer(issuerPath) - .setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(new PreAuthorizedCode().setPreAuthorizedCode("test-code"))) + .addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode("test-code")) .setCredentialConfigurationIds(List.of(jwtTypeCredentialConfigurationIdName)); CredentialOfferStorage offerStorage = session.getProvider(CredentialOfferStorage.class); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCGrantFeatureTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCGrantFeatureTest.java index fc42df955d1..d50bdeb8b8d 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCGrantFeatureTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oid4vc/issuance/signing/OID4VCGrantFeatureTest.java @@ -20,8 +20,8 @@ package org.keycloak.testsuite.oid4vc.issuance.signing; import org.keycloak.common.Profile; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oidc.grants.OAuth2GrantTypeSpi; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.feature.AbstractFeatureStateTest; @@ -35,7 +35,7 @@ public class OID4VCGrantFeatureTest extends AbstractFeatureStateTest { @Override public String getFeatureProviderId() { - return PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE; + return PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE; } @Override 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 f60ad2d178b..34d88f631bc 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 @@ -53,12 +53,10 @@ import org.keycloak.protocol.oid4vc.model.ErrorResponse; import org.keycloak.protocol.oid4vc.model.ErrorType; import org.keycloak.protocol.oid4vc.model.JwtProof; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedCode; -import org.keycloak.protocol.oid4vc.model.PreAuthorizedGrant; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oid4vc.model.Proofs; import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration; import org.keycloak.protocol.oid4vc.model.VerifiableCredential; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientRepresentation; @@ -238,7 +236,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest { authenticator.setTokenString(token); CredentialsOffer credOffer = new CredentialsOffer() .setCredentialIssuer("the-issuer") - .setGrants(new PreAuthorizedGrant().setPreAuthorizedCode(new PreAuthorizedCode().setPreAuthorizedCode("the-code"))) + .addGrant(new PreAuthorizedCodeGrant().setPreAuthorizedCode("the-code")) .setCredentialConfigurationIds(List.of("credential-configuration-id")); CredentialOfferStorage offerStorage = session.getProvider(CredentialOfferStorage.class); @@ -484,11 +482,11 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest { .getOidcConfiguration(); assertNotNull("A token endpoint should be included.", openidConfig.getTokenEndpoint()); - assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)); + assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)); // 5. Get an access token for the pre-authorized code AccessTokenResponse accessTokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(credentialsOffer.getPreAuthorizedCode()) .endpoint(openidConfig.getTokenEndpoint()) .send(); @@ -1222,8 +1220,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest { .send() .getCredentialsOffer(); assertNotNull("Credential offer should not be null", credentialsOffer); - assertNotNull("Pre-authorized grant should be present", credentialsOffer.getGrants()); - String preAuthorizedCode = credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode(); + String preAuthorizedCode = credentialsOffer.getPreAuthorizedCode(); assertNotNull("Pre-authorized code value should not be null", preAuthorizedCode); // 3. Second access to the same credential offer URL - should fail with replay protection error @@ -1347,8 +1344,7 @@ public class OID4VCJWTIssuerEndpointTest extends OID4VCIssuerEndpointTest { .send() .getCredentialsOffer(); assertNotNull("Credential offer should not be null", credentialsOffer); - assertNotNull("Pre-authorized grant should be present", credentialsOffer.getGrants()); - String preAuthorizedCode = credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode(); + String preAuthorizedCode = credentialsOffer.getPreAuthorizedCode(); assertNotNull("Pre-authorized code value should not be null", preAuthorizedCode); // 3. Immediately perform the Token Request (Pre-Authorized Code Grant) using the valid code 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 6cd992dc321..a72f2ca08a7 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 @@ -49,9 +49,9 @@ import org.keycloak.protocol.oid4vc.model.CredentialRequest; import org.keycloak.protocol.oid4vc.model.CredentialResponse; import org.keycloak.protocol.oid4vc.model.CredentialsOffer; import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail; +import org.keycloak.protocol.oid4vc.model.PreAuthorizedCodeGrant; import org.keycloak.protocol.oid4vc.model.Proofs; import org.keycloak.protocol.oid4vc.model.SupportedCredentialConfiguration; -import org.keycloak.protocol.oidc.grants.PreAuthorizedCodeGrantTypeFactory; import org.keycloak.protocol.oidc.representations.OIDCConfigurationRepresentation; import org.keycloak.representations.JsonWebToken; import org.keycloak.representations.idm.ClientScopeRepresentation; @@ -408,11 +408,11 @@ public class OID4VCSdJwtIssuingEndpointTest extends OID4VCIssuerEndpointTest { .getOidcConfiguration(); assertNotNull("A token endpoint should be included.", openidConfig.getTokenEndpoint()); - assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrantTypeFactory.GRANT_TYPE)); + assertTrue("The pre-authorized code should be supported.", openidConfig.getGrantTypesSupported().contains(PreAuthorizedCodeGrant.PRE_AUTH_GRANT_TYPE)); // 5. Get an access token for the pre-authorized code AccessTokenResponse accessTokenResponse = oauth.oid4vc() - .preAuthorizedCodeGrantRequest(credentialsOffer.getGrants().getPreAuthorizedCode().getPreAuthorizedCode()) + .preAuthorizedCodeGrantRequest(credentialsOffer.getPreAuthorizedCode()) .endpoint(openidConfig.getTokenEndpoint()) .send();