mirror of
https://github.com/keycloak/keycloak.git
synced 2025-12-16 20:15:46 -06:00
CredentialRequest with credentialIdentifier does not work when creden… (#44794)
closes #44793 Signed-off-by: mposolda <mposolda@gmail.com>
This commit is contained in:
@@ -137,18 +137,18 @@ public class OID4VCAuthorizationDetailsProcessor implements AuthorizationDetails
|
||||
// Use a reasonable expiration time (e.g., 1 hour)
|
||||
int expiration = Time.currentTime() + 3600;
|
||||
CredentialOfferStorage.CredentialOfferState offerState = new CredentialOfferStorage.CredentialOfferState(
|
||||
credOffer, client.getClientId(), user.getUsername(), expiration);
|
||||
credOffer, client.getClientId(), user.getId(), expiration);
|
||||
offerState.setAuthorizationDetails(oid4vcDetail);
|
||||
|
||||
offerStorage.putOfferState(session, offerState);
|
||||
logger.debugf("Created credential offer state for authorization code flow: [cid=%s, uid=%s, credConfigId=%s, credId=%s]",
|
||||
client.getClientId(), user.getUsername(), oid4vcDetail.getCredentialConfigurationId(), credentialId);
|
||||
client.getClientId(), offerState.getUserId(), oid4vcDetail.getCredentialConfigurationId(), credentialId);
|
||||
} else {
|
||||
// Update existing offer state with new authorization details (e.g., if same credential identifier is reused)
|
||||
existingState.setAuthorizationDetails(oid4vcDetail);
|
||||
offerStorage.replaceOfferState(session, existingState);
|
||||
logger.debugf("Updated existing credential offer state for authorization code flow: [cid=%s, uid=%s, credConfigId=%s, credId=%s]",
|
||||
client.getClientId(), user.getUsername(), oid4vcDetail.getCredentialConfigurationId(), credentialId);
|
||||
client.getClientId(), existingState.getUserId(), oid4vcDetail.getCredentialConfigurationId(), credentialId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import jakarta.ws.rs.core.HttpHeaders;
|
||||
|
||||
@@ -34,7 +35,6 @@ import org.keycloak.protocol.oid4vc.model.ClaimsDescription;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
|
||||
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.oidc.representations.OIDCConfigurationRepresentation;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.ClientScopeRepresentation;
|
||||
@@ -70,7 +70,6 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
*/
|
||||
protected static class Oid4vcTestContext {
|
||||
public CredentialIssuer credentialIssuer;
|
||||
public CredentialsOffer credentialsOffer;
|
||||
public OIDCConfigurationRepresentation openidConfig;
|
||||
}
|
||||
|
||||
@@ -92,7 +91,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
/**
|
||||
* Prepare OID4VC test context by fetching issuer metadata and credential offer
|
||||
*/
|
||||
protected Oid4vcTestContext prepareOid4vcTestContext(String token) throws Exception {
|
||||
protected Oid4vcTestContext prepareOid4vcTestContext() throws Exception {
|
||||
Oid4vcTestContext ctx = new Oid4vcTestContext();
|
||||
|
||||
// Get credential issuer metadata
|
||||
@@ -114,9 +113,33 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
return ctx;
|
||||
}
|
||||
|
||||
// Test for the whole authorization_code flow with the credentialRequest using credential_configuration_id
|
||||
@Test
|
||||
public void testCompleteFlowWithClaimsValidationAuthorizationCode() throws Exception {
|
||||
Oid4vcTestContext ctx = prepareOid4vcTestContext(null);
|
||||
public void testCompleteFlowWithClaimsValidationAuthorizationCode_credentialRequestWithConfigurationId() throws Exception {
|
||||
BiFunction<String, String, CredentialRequest> credRequestSupplier = (credentialConfigurationId, credentialIdentifier) -> {
|
||||
CredentialRequest credentialRequest = new CredentialRequest();
|
||||
credentialRequest.setCredentialConfigurationId(credentialConfigurationId);
|
||||
return credentialRequest;
|
||||
};
|
||||
|
||||
testCompleteFlowWithClaimsValidationAuthorizationCode(credRequestSupplier);
|
||||
}
|
||||
|
||||
// Test for the whole authorization_code flow with the credentialRequest using credential_identifier
|
||||
@Test
|
||||
public void testCompleteFlowWithClaimsValidationAuthorizationCode_credentialRequestWithCredentialIdentifier() throws Exception {
|
||||
BiFunction<String, String, CredentialRequest> credRequestSupplier = (credentialConfigurationId, credentialIdentifier) -> {
|
||||
CredentialRequest credentialRequest = new CredentialRequest();
|
||||
credentialRequest.setCredentialIdentifier(credentialIdentifier);
|
||||
return credentialRequest;
|
||||
};
|
||||
|
||||
testCompleteFlowWithClaimsValidationAuthorizationCode(credRequestSupplier);
|
||||
}
|
||||
|
||||
|
||||
private void testCompleteFlowWithClaimsValidationAuthorizationCode(BiFunction<String, String, CredentialRequest> credentialRequestSupplier) throws Exception {
|
||||
Oid4vcTestContext ctx = prepareOid4vcTestContext();
|
||||
|
||||
// Perform authorization code flow to get authorization code
|
||||
oauth.client(client.getClientId());
|
||||
@@ -132,7 +155,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
// Construct claim path based on credential format
|
||||
List<Object> claimPath;
|
||||
if ("sd_jwt_vc".equals(getCredentialFormat())) {
|
||||
claimPath = Arrays.asList(getExpectedClaimPath());
|
||||
claimPath = Collections.singletonList(getExpectedClaimPath());
|
||||
} else {
|
||||
claimPath = Arrays.asList("credentialSubject", getExpectedClaimPath());
|
||||
}
|
||||
@@ -142,7 +165,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
AuthorizationDetail authDetail = new AuthorizationDetail();
|
||||
authDetail.setType(OPENID_CREDENTIAL);
|
||||
authDetail.setCredentialConfigurationId(getCredentialClientScope().getAttributes().get(CredentialScopeModel.CONFIGURATION_ID));
|
||||
authDetail.setClaims(Arrays.asList(claim));
|
||||
authDetail.setClaims(List.of(claim));
|
||||
authDetail.setLocations(Collections.singletonList(ctx.credentialIssuer.getCredentialIssuer()));
|
||||
|
||||
List<AuthorizationDetail> authDetails = List.of(authDetail);
|
||||
@@ -156,7 +179,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
tokenParameters.add(new BasicNameValuePair(OAuth2Constants.REDIRECT_URI, oauth.getRedirectUri()));
|
||||
tokenParameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, oauth.getClientId()));
|
||||
tokenParameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_SECRET, "password"));
|
||||
tokenParameters.add(new BasicNameValuePair("authorization_details", authDetailsJson));
|
||||
tokenParameters.add(new BasicNameValuePair(OAuth2Constants.AUTHORIZATION_DETAILS, authDetailsJson));
|
||||
UrlEncodedFormEntity tokenFormEntity = new UrlEncodedFormEntity(tokenParameters, StandardCharsets.UTF_8);
|
||||
postToken.setEntity(tokenFormEntity);
|
||||
|
||||
@@ -179,13 +202,18 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
String credentialConfigurationId = authDetailResponse.getCredentialConfigurationId();
|
||||
assertNotNull("Credential configuration id should not be null", credentialConfigurationId);
|
||||
|
||||
List<String> credentialIdentifiers = authDetailResponse.getCredentialIdentifiers();
|
||||
assertNotNull("Credential identifiers should not be null", credentialIdentifiers);
|
||||
assertEquals("Credential identifiers expected to have 1 item. It had " + credentialIdentifiers.size() + " with value " + credentialIdentifiers,
|
||||
1, credentialIdentifiers.size());
|
||||
String credentialIdentifier = credentialIdentifiers.get(0);
|
||||
|
||||
// Request the actual credential using the identifier
|
||||
HttpPost postCredential = new HttpPost(ctx.credentialIssuer.getCredentialEndpoint());
|
||||
postCredential.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + tokenResponse.getToken());
|
||||
postCredential.addHeader(HttpHeaders.CONTENT_TYPE, "application/json");
|
||||
|
||||
CredentialRequest credentialRequest = new CredentialRequest();
|
||||
credentialRequest.setCredentialConfigurationId(credentialConfigurationId);
|
||||
CredentialRequest credentialRequest = credentialRequestSupplier.apply(credentialConfigurationId, credentialIdentifier);
|
||||
|
||||
String requestBody = JsonSerialization.writeValueAsString(credentialRequest);
|
||||
postCredential.setEntity(new StringEntity(requestBody, StandardCharsets.UTF_8));
|
||||
@@ -228,8 +256,9 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
protected List<OID4VCAuthorizationDetailsResponse> parseAuthorizationDetails(String responseBody) {
|
||||
try {
|
||||
// Parse the JSON response to extract authorization_details
|
||||
Map<String, Object> responseMap = JsonSerialization.readValue(responseBody, Map.class);
|
||||
Object authDetailsObj = responseMap.get("authorization_details");
|
||||
Map<String, Object> responseMap = JsonSerialization.readValue(responseBody, new TypeReference<>() {
|
||||
});
|
||||
Object authDetailsObj = responseMap.get(OAuth2Constants.AUTHORIZATION_DETAILS);
|
||||
|
||||
if (authDetailsObj == null) {
|
||||
return Collections.emptyList();
|
||||
@@ -237,7 +266,7 @@ public abstract class OID4VCAuthorizationCodeFlowTestBase extends OID4VCIssuerEn
|
||||
|
||||
// Convert to list of OID4VCAuthorizationDetailsResponse
|
||||
return JsonSerialization.readValue(JsonSerialization.writeValueAsString(authDetailsObj),
|
||||
new TypeReference<List<OID4VCAuthorizationDetailsResponse>>() {
|
||||
new TypeReference<>() {
|
||||
});
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Failed to parse authorization_details from response", e);
|
||||
|
||||
Reference in New Issue
Block a user