mirror of
https://github.com/keycloak/keycloak.git
synced 2026-06-08 16:42:13 -04:00
Merge f7d5d5cfdf into 94dcc24a8d
This commit is contained in:
commit
2efd4fedf4
10 changed files with 66 additions and 7 deletions
|
|
@ -18,6 +18,8 @@
|
|||
package org.keycloak.protocol.oid4vc.issuance.credentialbuilder;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.UnaryOperator;
|
||||
|
|
@ -71,6 +73,8 @@ public class JwtCredentialBuilder implements CredentialBuilder {
|
|||
VerifiableCredential verifiableCredential,
|
||||
CredentialBuildConfig credentialBuildConfig
|
||||
) throws CredentialBuilderException {
|
||||
verifiableCredential.setType(getCredentialTypes(verifiableCredential.getType()));
|
||||
|
||||
// Populate the issuer field of the VC
|
||||
verifiableCredential.setIssuer(credentialBuildConfig.getCredentialIssuer());
|
||||
|
||||
|
|
@ -108,6 +112,14 @@ public class JwtCredentialBuilder implements CredentialBuilder {
|
|||
return new JwtCredentialBody(jwsBuilder);
|
||||
}
|
||||
|
||||
private static List<String> getCredentialTypes(List<String> credentialTypes) {
|
||||
List<String> types = new ArrayList<>(Optional.ofNullable(credentialTypes).orElseGet(List::of));
|
||||
if (!types.contains(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE)) {
|
||||
types.add(0, CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE);
|
||||
}
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contributeToMetadata(SupportedCredentialConfiguration credentialConfig, CredentialScopeModel credentialScope) {
|
||||
CredentialDefinition credentialDefinition = CredentialDefinition.parse(credentialScope);
|
||||
|
|
|
|||
|
|
@ -37,14 +37,23 @@ public class CredentialDefinition {
|
|||
private List<String> context;
|
||||
private List<String> type = new ArrayList<>();
|
||||
|
||||
public static final String VERIFIABLE_CREDENTIAL_TYPE = "VerifiableCredential";
|
||||
|
||||
public static CredentialDefinition parse(CredentialScopeModel credentialModel) {
|
||||
List<String> contexts = Optional.of(credentialModel.getVcContexts())
|
||||
.filter(list -> !list.isEmpty())
|
||||
.orElseGet(() -> new ArrayList<>(List.of(credentialModel.getName())));
|
||||
List<String> types = Optional.ofNullable(credentialModel.getSupportedCredentialTypes())
|
||||
.filter(list -> !list.isEmpty())
|
||||
.map(ArrayList::new)
|
||||
.orElseGet(() -> new ArrayList<>(List.of(credentialModel.getName())));
|
||||
|
||||
// Ensure VerifiableCredential is always included as a base type
|
||||
// per the W3C Verifiable Credentials Data Model specification.
|
||||
if (!types.contains(VERIFIABLE_CREDENTIAL_TYPE)) {
|
||||
types.add(0, VERIFIABLE_CREDENTIAL_TYPE);
|
||||
}
|
||||
|
||||
return new CredentialDefinition().setContext(contexts)
|
||||
.setType(types);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package org.keycloak.tests.oid4vc;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
|
@ -8,6 +9,7 @@ import java.util.stream.Collectors;
|
|||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.jose.jwk.JWK;
|
||||
import org.keycloak.jose.jwk.JWKBuilder;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialScopeRepresentation;
|
||||
import org.keycloak.protocol.oid4vc.model.Proofs;
|
||||
|
|
@ -141,7 +143,10 @@ public class OID4VCINaturalPersonTest extends OID4VCIssuerTestBase {
|
|||
assertEquals(issuer, vcJwt.getIssuer());
|
||||
Object vc = vcJwt.getOtherClaims().get("vc");
|
||||
VerifiableCredential credential = JsonSerialization.mapper.convertValue(vc, VerifiableCredential.class);
|
||||
assertEquals(credScope.getSupportedCredentialTypes(), credential.getType());
|
||||
List<String> expectedCredentialTypes = new ArrayList<>();
|
||||
expectedCredentialTypes.add(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE);
|
||||
expectedCredentialTypes.addAll(credScope.getSupportedCredentialTypes());
|
||||
assertEquals(expectedCredentialTypes, credential.getType());
|
||||
assertEquals(URI.create(issuer), credential.getIssuer());
|
||||
assertEquals(expUser + "@email.cz", credential.getCredentialSubject().getClaims().get("email"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import org.keycloak.protocol.oid4vc.issuance.mappers.OID4VCMapper;
|
|||
import org.keycloak.protocol.oid4vc.model.Claim;
|
||||
import org.keycloak.protocol.oid4vc.model.ClaimDisplay;
|
||||
import org.keycloak.protocol.oid4vc.model.Claims;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialRequestEncryptionMetadata;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponseEncryptionMetadata;
|
||||
|
|
@ -667,9 +668,13 @@ public class OID4VCIssuerWellKnownProviderTest extends OID4VCIssuerTestBase {
|
|||
assertNull(supportedConfig.getVct(), "JWT_VC credentials should not have vct");
|
||||
assertNotNull(supportedConfig.getCredentialDefinition());
|
||||
assertNotNull(supportedConfig.getCredentialDefinition().getType());
|
||||
// VerifiableCredential must always be present as a base type per W3C VC Data Model
|
||||
MatcherAssert.assertThat(supportedConfig.getCredentialDefinition().getType(),
|
||||
Matchers.hasItem(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE));
|
||||
List<String> credentialDefinitionTypes = credScope.getSupportedCredentialTypes();
|
||||
if (!credentialDefinitionTypes.isEmpty()) {
|
||||
assertEquals(credentialDefinitionTypes.size(), supportedConfig.getCredentialDefinition().getType().size());
|
||||
MatcherAssert.assertThat(supportedConfig.getCredentialDefinition().getType(),
|
||||
Matchers.hasItems(credentialDefinitionTypes.toArray(new String[0])));
|
||||
}
|
||||
|
||||
// @context must not be present for jwt_vc_json format per OID4VCI spec
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.net.URI;
|
|||
import java.util.List;
|
||||
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
|
||||
import org.keycloak.protocol.oid4vc.model.OID4VCAuthorizationDetail;
|
||||
|
|
@ -152,7 +153,7 @@ public class OID4VCPublicClientTest extends OID4VCIssuerTestBase {
|
|||
assertEquals("did:web:test.org", jsonWebToken.getIssuer());
|
||||
Object vc = jsonWebToken.getOtherClaims().get("vc");
|
||||
VerifiableCredential credential = JsonSerialization.mapper.convertValue(vc, VerifiableCredential.class);
|
||||
assertEquals(List.of(scope), credential.getType());
|
||||
assertEquals(List.of(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE, scope), credential.getType());
|
||||
assertEquals(URI.create("did:web:test.org"), credential.getIssuer());
|
||||
assertEquals(expUser + "@email.cz", credential.getCredentialSubject().getClaims().get("email"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import org.keycloak.TokenVerifier;
|
|||
import org.keycloak.admin.client.resource.ClientPoliciesPoliciesResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.protocol.oid4vc.clientpolicy.PredicateCredentialClientPolicy;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialIssuer;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
|
||||
|
|
@ -213,7 +214,7 @@ public class OID4VCredentialByScopeTest extends OID4VCIssuerTestBase {
|
|||
assertEquals("did:web:test.org", jsonWebToken.getIssuer());
|
||||
Object vc = jsonWebToken.getOtherClaims().get("vc");
|
||||
VerifiableCredential credential = JsonSerialization.mapper.convertValue(vc, VerifiableCredential.class);
|
||||
assertEquals(List.of(scope), credential.getType());
|
||||
assertEquals(List.of(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE, scope), credential.getType());
|
||||
assertEquals(URI.create("did:web:test.org"), credential.getIssuer());
|
||||
assertEquals(expUser + "@email.cz", credential.getCredentialSubject().getClaims().get("email"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import java.net.URI;
|
|||
import java.util.List;
|
||||
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialOfferURI;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
|
||||
|
|
@ -317,7 +318,7 @@ public class OID4VCredentialOfferAuthCodeTest extends OID4VCIssuerTestBase {
|
|||
assertEquals("did:web:test.org", jsonWebToken.getIssuer());
|
||||
Object vc = jsonWebToken.getOtherClaims().get("vc");
|
||||
VerifiableCredential credential = JsonSerialization.mapper.convertValue(vc, VerifiableCredential.class);
|
||||
assertEquals(List.of(scope), credential.getType());
|
||||
assertEquals(List.of(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE, scope), credential.getType());
|
||||
assertEquals(URI.create("did:web:test.org"), credential.getIssuer());
|
||||
assertEquals(expUser + "@email.cz", credential.getCredentialSubject().getClaims().get("email"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import org.keycloak.protocol.oid4vc.issuance.TimeProvider;
|
|||
import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.JwtCredentialBody;
|
||||
import org.keycloak.protocol.oid4vc.issuance.credentialbuilder.JwtCredentialBuilder;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialBuildConfig;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.VerifiableCredential;
|
||||
import org.keycloak.testframework.annotations.KeycloakIntegrationTest;
|
||||
import org.keycloak.tests.oid4vc.OID4VCIssuerTestBase;
|
||||
|
|
@ -93,11 +94,33 @@ public class JwtCredentialBuilderTest extends CredentialBuilderTest {
|
|||
assertEquals(expectedNormalizedNbf, payload.get("nbf").asLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildJwtCredential_AddsVerifiableCredentialType() throws Exception {
|
||||
VerifiableCredential verifiableCredential = getTestCredential(exampleCredentialClaims());
|
||||
verifiableCredential.setType(List.of("oid4vc_natural_person"));
|
||||
CredentialBuildConfig credentialBuildConfig = new CredentialBuildConfig().setTokenJwsType("JWT");
|
||||
|
||||
JwtCredentialBody jwtCredentialBody = builder
|
||||
.buildCredentialBody(verifiableCredential, credentialBuildConfig);
|
||||
|
||||
String jws = jwtCredentialBody.sign(exampleSigner());
|
||||
JWSInput jwsInput = new JWSInput(jws);
|
||||
JsonNode credentialTypes = parseCredentialTypes(jwsInput);
|
||||
|
||||
assertEquals(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE, credentialTypes.get(0).asText());
|
||||
assertEquals("oid4vc_natural_person", credentialTypes.get(1).asText());
|
||||
}
|
||||
|
||||
private JsonNode parseCredentialSubject(JWSInput jwsInput) throws JWSInputException {
|
||||
JsonNode payload = jwsInput.readJsonContent(JsonNode.class);
|
||||
return payload.get("vc").get(CREDENTIAL_SUBJECT);
|
||||
}
|
||||
|
||||
private JsonNode parseCredentialTypes(JWSInput jwsInput) throws JWSInputException {
|
||||
JsonNode payload = jwsInput.readJsonContent(JsonNode.class);
|
||||
return payload.get("vc").get("type");
|
||||
}
|
||||
|
||||
private Map<String, Object> exampleCredentialClaims() {
|
||||
return new HashMap<>(Map.of(
|
||||
"id", String.format("uri:uuid:%s", UUID.randomUUID()),
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import java.net.URI;
|
|||
import java.util.List;
|
||||
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
|
||||
import org.keycloak.protocol.oid4vc.model.VerifiableCredential;
|
||||
|
|
@ -98,7 +99,7 @@ public class OID4VCPublicClientPreAuthTest extends OID4VCIssuerTestBase {
|
|||
assertEquals("did:web:test.org", jsonWebToken.getIssuer());
|
||||
Object vc = jsonWebToken.getOtherClaims().get("vc");
|
||||
VerifiableCredential credential = JsonSerialization.mapper.convertValue(vc, VerifiableCredential.class);
|
||||
assertEquals(List.of(scope), credential.getType());
|
||||
assertEquals(List.of(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE, scope), credential.getType());
|
||||
assertEquals(URI.create("did:web:test.org"), credential.getIssuer());
|
||||
assertEquals(expUser + "@email.cz", credential.getCredentialSubject().getClaims().get("email"));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import java.util.List;
|
|||
|
||||
import org.keycloak.TokenVerifier;
|
||||
import org.keycloak.admin.client.resource.UserResource;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialDefinition;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialOfferURI;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialResponse;
|
||||
import org.keycloak.protocol.oid4vc.model.CredentialsOffer;
|
||||
|
|
@ -161,7 +162,7 @@ public class OID4VCredentialOfferPreAuthTest extends OID4VCIssuerTestBase {
|
|||
assertEquals("did:web:test.org", jsonWebToken.getIssuer());
|
||||
Object vc = jsonWebToken.getOtherClaims().get("vc");
|
||||
VerifiableCredential credential = JsonSerialization.mapper.convertValue(vc, VerifiableCredential.class);
|
||||
assertEquals(List.of(scope), credential.getType());
|
||||
assertEquals(List.of(CredentialDefinition.VERIFIABLE_CREDENTIAL_TYPE, scope), credential.getType());
|
||||
assertEquals(URI.create("did:web:test.org"), credential.getIssuer());
|
||||
assertEquals(expUser + "@email.cz", credential.getCredentialSubject().getClaims().get("email"));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue