8000 [OID4VCI] Credential Offer must be created by Issuer not Holder by tdiesler · Pull Request #44255 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
# Intellij
###################
.idea
.run
*.iml
!.idea/icon.png

Expand Down
6 changes: 5 additions & 1 deletion core/src/main/java/org/keycloak/OAuth2Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ public interface OAuth2Constants {
String CNF = "cnf";

// RAR - https://datatracker.ietf.org/doc/html/rfc9396
String AUTHORIZATION_DETAILS_PARAM = "authorization_details";
// Used as url parameter in AuthorizationRequest and as claim in TokenResponse
String AUTHORIZATION_DETAILS = "authorization_details";

// DPoP - https://datatracker.ietf.org/doc/html/rfc9449
String DPOP_HTTP_HEADER = "DPoP";
Expand All @@ -173,4 +174,7 @@ public interface OAuth2Constants {
String DPOP_JWT_HEADER_TYPE = "dpop+jwt";
String ALGS_ATTRIBUTE = "algs";

// OID4VCI - https://openid.net/specs/openid-4-verifiable-credential-issuance-1_0.html
String OPENID_CREDENTIAL = "openid_credential";
String CREDENTIAL_IDENTIFIERS = "credential_identifiers";
}
16 changes: 16 additions & 0 deletions core/src/main/java/org/keycloak/util/JsonSerialization.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,22 @@ public class JsonSerialization {
prettyMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
}

public static String valueAsString(Object obj) {
try {
return mapper.writeValueAsString(obj);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}

public static <T> T valueFromString(String string, Class<T> type) {
try {
return mapper.readValue(string, type);
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}

public static void writeValueToStream(OutputStream os, Object obj) throws IOException {
mapper.writeValue(os, obj);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@

package org.keycloak.constants;

import org.keycloak.representations.idm.RoleRepresentation;

/**
* Keycloak specific constants related to OID4VC and related functionality. Useful for example for internal constants (EG. name of Keycloak realm attributes).
* For protocol constants defined in the specification, see {@link org.keycloak.OID4VCConstants}
*
* @author Pascal Knüppel
*/
public final class Oid4VciConstants {
public final class OID4VCIConstants {

public static final String OID4VC_PROTOCOL = "oid4vc";

Expand All @@ -38,6 +40,9 @@ public final class Oid4VciConstants {
public static final String SOURCE_ENDPOINT = "source_endpoint";
public static final String BATCH_CREDENTIAL_ISSUANCE_BATCH_SIZE = "batch_credential_issuance.batch_size";

private Oid4VciConstants() {
public static final RoleRepresentation CREDENTIAL_OFFER_CREATE =
new RoleRepresentation("credential-offer-create", "Allow credential offer creation", false);

private OID4VCIConstants() {
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@
import java.util.Optional;
import java.util.stream.Stream;

import org.keycloak.constants.Oid4VciConstants;
import org.keycloak.constants.OID4VCIConstants;
import org.keycloak.models.ClientScopeModel;
import org.keycloak.models.ProtocolMapperModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;

import static org.keycloak.OID4VCConstants.SD_JWT_VC_FORMAT;
import static org.keycloak.constants.Oid4VciConstants.OID4VC_PROTOCOL;
import static org.keycloak.constants.OID4VCIConstants.OID4VC_PROTOCOL;

/**
* This class acts as delegate for a {@link ClientScopeModel} implementation and adds additional functionality for
Expand Down Expand Up @@ -424,7 +424,7 @@ public String getDynamicScopeRegexp() {

public Stream<Oid4vcProtocolMapperModel> getOid4vcProtocolMappersStream() {
return clientScope.getProtocolMappersStream().filter(pm -> {
return Oid4VciConstants.OID4VC_PROTOCOL.equals(pm.getProtocol());
return OID4VCIConstants.OID4VC_PROTOCOL.equals(pm.getProtocol());
}).map(Oid4vcProtocolMapperModel::new);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
import org.keycloak.common.util.SecretGenerator;
import org.keycloak.common.util.Time;
import org.keycloak.component.ComponentModel;
import org.keycloak.constants.Oid4VciConstants;
import org.keycloak.constants.OID4VCIConstants;
import org.keycloak.crypto.Algorithm;
import org.keycloak.deployment.DeployedConfigurationsManager;
import org.keycloak.models.AccountRoles;
Expand Down Expand Up @@ -1241,7 +1241,7 @@ public static <T> T runOnRealm(KeycloakSession session, RealmModel target, Funct
public static List<String> getAcceptedClientScopeProtocols(ClientModel client) {
List<String> acceptedClientProtocols;
if (client.getProtocol() == null || "openid-connect".equals(client.getProtocol())) {
acceptedClientProtocols = List.of("openid-connect", Oid4VciConstants.OID4VC_PROTOCOL);
acceptedClientProtocols = List.of("openid-connect", OID4VCIConstants.OID4VC_PROTOCOL);
}else {
acceptedClientProtocols = List.of(client.getProtocol());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import java.util.Map;

import org.keycloak.Config;
import org.keycloak.constants.Oid4VciConstants;
import org.keycloak.constants.OID4VCIConstants;
import org.keycloak.events.EventBuilder;
import org.keycloak.models.ClientModel;
import org.keycloak.models.ClientScopeModel;
Expand All @@ -43,7 +43,7 @@

import org.jboss.logging.Logger;

import static org.keycloak.constants.Oid4VciConstants.OID4VC_PROTOCOL;
import static org.keycloak.constants.OID4VCIConstants.OID4VC_PROTOCOL;
import static org.keycloak.models.ClientScopeModel.INCLUDE_IN_TOKEN_SCOPE;
import static org.keycloak.models.oid4vci.CredentialScopeModel.CONFIGURATION_ID;
import static org.keycloak.models.oid4vci.CredentialScopeModel.CONTEXTS;
Expand Down Expand Up @@ -82,7 +82,7 @@ public class OID4VCLoginProtocolFactory implements LoginProtocolFactory, OID4VCE
private static final String LAST_NAME_MAPPER = "last-name";
private static final String FIRST_NAME_MAPPER = "first-name";

public static final String PROTOCOL_ID = Oid4VciConstants.OID4VC_PROTOCOL;
public static final String PROTOCOL_ID = OID4VCIConstants.OID4VC_PROTOCOL;

private final Map<String, ProtocolMapperModel> builtins = new HashMap<>();

Expand Down Expand Up @@ -181,6 +181,8 @@ public int order() {
return OIDCLoginProtocolFactory.UI_ORDER - 20;
}

// Private ---------------------------------------------------------------------------------------------------------

private void addClientScopeDefaults(ClientScopeModel clientScope) {
ClientScopeRepresentation clientScopeRep = ModelToRepresentation.toRepresentation(clientScope);
addClientScopeDefaults(clientScopeRep);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,13 @@
import com.fasterxml.jackson.core.type.TypeReference;
import org.jboss.logging.Logger;

import static org.keycloak.OAuth2Constants.OPENID_CREDENTIAL;
import static org.keycloak.models.Constants.AUTHORIZATION_DETAILS_RESPONSE;

public class OID4VCAuthorizationDetailsProcessor implements AuthorizationDetailsProcessor {
private static final Logger logger = Logger.getLogger(OID4VCAuthorizationDetailsProcessor.class);
private final KeycloakSession session;

public static final String OPENID_CREDENTIAL_TYPE = "openid_credential";

public OID4VCAuthorizationDetailsProcessor(KeycloakSession session) {
this.session = session;
}
Expand Down Expand Up @@ -114,9 +113,9 @@ private void validateAuthorizationDetail(AuthorizationDetail detail, Map<String,
List<ClaimsDescription> claims = detail.getClaims();

// Validate type first
if (!OPENID_CREDENTIAL_TYPE.equals(type)) {
if (!OPENID_CREDENTIAL.equals(type)) {
logger.warnf("Invalid authorization_details type: %s", type);
throw getInvalidRequestException("type: " + type + ", expected=" + OPENID_CREDENTIAL_TYPE);
throw getInvalidRequestException("type: " + type + ", expected=" + OPENID_CREDENTIAL);
}

// If authorization_servers is present, locations must be set to issuer identifier
Expand Down Expand Up @@ -222,19 +221,10 @@ private AuthorizationDetailsResponse buildAuthorizationDetailResponse(Authorizat
}

OID4VCAuthorizationDetailsResponse responseDetail = new OID4VCAuthorizationDetailsResponse();
responseDetail.setType(OPENID_CREDENTIAL_TYPE);
responseDetail.setType(OPENID_CREDENTIAL);
responseDetail.setCredentialConfigurationId(credentialConfigurationId);
responseDetail.setCredentialIdentifiers(credentialIdentifiers);

// Store credential identifier mapping in client session for later use during credential issuance
AuthenticatedClientSessionM 10BC8 odel clientSession = clientSessionCtx.getClientSession();
for (String credentialIdentifier : credentialIdentifiers) {
// Store the mapping between credential identifier and configuration ID in client session
String mappingKey = OID4VCIssuerEndpoint.CREDENTIAL_IDENTIFIER_PREFIX + credentialIdentifier;
clientSession.setNote(mappingKey, credentialConfigurationId);
logger.debugf("Stored credential identifier mapping: %s -> %s", credentialIdentifier, credentialConfigurationId);
}

// Store claims in user session for later use during credential issuance
if (detail.getClaims() != null) {
// Store claims with a unique key based on credential configuration ID
Expand Down Expand Up @@ -289,17 +279,11 @@ private List<AuthorizationDetailsResponse> generateAuthorizationDetailsFromCrede
}

String credentialIdentifier = UUID.randomUUID().toString();

// Store the mapping between credential identifier and configuration ID in client session
// This will be used later when processing credential requests
String mappingKey = OID4VCIssuerEndpoint.CREDENTIAL_IDENTIFIER_PREFIX + credentialIdentifier;
clientSession.setNote(mappingKey, credentialConfigurationId);

logger.debugf("Generated credential identifier '%s' for configuration '%s'",
credentialIdentifier, credentialConfigurationId);

OID4VCAuthorizationDetailsResponse authDetail = new OID4VCAuthorizationDetailsResponse();
authDetail.setType(OPENID_CREDENTIAL_TYPE);
authDetail.setType(OPENID_CREDENTIAL);
authDetail.setCredentialConfigurationId(credentialConfigurationId);
authDetail.setCredentialIdentifiers(List.of(credentialIdentifier));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessor;
import org.keycloak.protocol.oidc.rar.AuthorizationDetailsProcessorFactory;

import static org.keycloak.OAuth2Constants.OPENID_CREDENTIAL;

/**
* Factory for creating OID4VCI-specific authorization details processors.
* This factory is only enabled when the OID4VCI feature is available.
Expand All @@ -31,7 +33,7 @@
*/
public class OID4VCAuthorizationDetailsProcessorFactory implements AuthorizationDetailsProcessorFactory, OID4VCEnvironmentProviderFactory {

public static final String PROVIDER_ID = OID4VCAuthorizationDetailsProcessor.OPENID_CREDENTIAL_TYPE;
public static final String PROVIDER_ID = OPENID_CREDENTIAL;

@Override
public AuthorizationDetailsProcessor create(KeycloakSession session) {
Expand Down
Loading
Loading
0