From 1a4ee48ec3fb8dd433b72dfadf486ac2d32267c4 Mon Sep 17 00:00:00 2001 From: Thomas Diesler Date: Thu, 9 Apr 2026 12:30:23 +0200 Subject: [PATCH] [OID4VCI] Default client scopes not configured correctly Signed-off-by: Thomas Diesler --- .../oid4vc/OID4VCLoginProtocolFactory.java | 9 +- .../admin/client/ClientScopeTestOid4Vci.java | 164 ------------ .../tests/oid4vc/OID4VCClientScopeTest.java | 236 ++++++++++++++++++ 3 files changed, 244 insertions(+), 165 deletions(-) delete mode 100644 tests/base/src/test/java/org/keycloak/tests/admin/client/ClientScopeTestOid4Vci.java create mode 100644 tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCClientScopeTest.java diff --git a/services/src/main/java/org/keycloak/protocol/oid4vc/OID4VCLoginProtocolFactory.java b/services/src/main/java/org/keycloak/protocol/oid4vc/OID4VCLoginProtocolFactory.java index a718c3ea385e..aa27d2ed77e7 100644 --- a/services/src/main/java/org/keycloak/protocol/oid4vc/OID4VCLoginProtocolFactory.java +++ b/services/src/main/java/org/keycloak/protocol/oid4vc/OID4VCLoginProtocolFactory.java @@ -41,6 +41,7 @@ import org.keycloak.protocol.oid4vc.issuance.OID4VCIssuerEndpoint; import org.keycloak.protocol.oid4vc.issuance.mappers.OID4VCSubjectIdMapper; import org.keycloak.protocol.oid4vc.issuance.mappers.OID4VCUserAttributeMapper; +import org.keycloak.protocol.oid4vc.model.ProofType; import org.keycloak.protocol.oidc.OIDCLoginProtocolFactory; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.ClientScopeRepresentation; @@ -55,6 +56,8 @@ import static org.keycloak.models.ClientScopeModel.INCLUDE_IN_TOKEN_SCOPE; import static org.keycloak.models.oid4vci.CredentialScopeModel.CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT; import static org.keycloak.models.oid4vci.CredentialScopeModel.VCT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BINDING_REQUIRED; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BINDING_REQUIRED_PROOF_TYPES; import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_HASH_ALGORITHM; import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_HASH_ALGORITHM_DEFAULT; import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS; @@ -81,7 +84,6 @@ public class OID4VCLoginProtocolFactory implements LoginProtocolFactory, OID4VCE private static final Logger LOGGER = Logger.getLogger(OID4VCLoginProtocolFactory.class); - private static final String CLIENT_ROLES_MAPPER = "client-roles"; private static final String USERNAME_MAPPER = "username"; private static final String SUBJECT_ID_MAPPER = "subject-id"; private static final String EMAIL_MAPPER = "email"; @@ -141,6 +143,11 @@ public void createDefaultClientScopes(RealmModel newRealm, boolean addScopesToEx clientScope.addProtocolMapper(builtins.get(LAST_NAME_MAPPER)); ClientScopeRepresentation clientScopeRep = ModelToRepresentation.toRepresentation(clientScope); + clientScopeRep.setAttributes(new HashMap<>(Map.of( + VC_BINDING_REQUIRED, "true", + VC_BINDING_REQUIRED_PROOF_TYPES, ProofType.JWT + "," + ProofType.ATTESTATION + ))); + addClientScopeDefaults(clientScopeRep); RepresentationToModel.updateClientScope(clientScopeRep, clientScope); } diff --git a/tests/base/src/test/java/org/keycloak/tests/admin/client/ClientScopeTestOid4Vci.java b/tests/base/src/test/java/org/keycloak/tests/admin/client/ClientScopeTestOid4Vci.java deleted file mode 100644 index 8c0027bf34dd..000000000000 --- a/tests/base/src/test/java/org/keycloak/tests/admin/client/ClientScopeTestOid4Vci.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright 2025 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.tests.admin.client; - -import java.util.Map; - -import jakarta.ws.rs.BadRequestException; -import jakarta.ws.rs.core.Response; - -import org.keycloak.admin.client.resource.ClientScopeResource; -import org.keycloak.common.Profile; -import org.keycloak.constants.OID4VCIConstants; -import org.keycloak.models.oid4vci.CredentialScopeModel; -import org.keycloak.representations.idm.ClientScopeRepresentation; -import org.keycloak.representations.idm.RealmRepresentation; -import org.keycloak.testframework.annotations.KeycloakIntegrationTest; -import org.keycloak.testframework.server.KeycloakServerConfig; -import org.keycloak.testframework.server.KeycloakServerConfigBuilder; -import org.keycloak.testframework.util.ApiUtil; - -import org.junit.Assert; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -/** - * @author Pascal Knüppel - */ -@KeycloakIntegrationTest(config = ClientScopeTestOid4Vci.DefaultServerConfigWithOid4Vci.class) -public class ClientScopeTestOid4Vci extends AbstractClientScopeTest { - - @BeforeEach - public void enableVerifiableCredentialsInRealm() { - // Enable verifiable credentials on the realm - // This is required in addition to the server feature being enabled - managedRealm.updateWithCleanup(r -> r.update(rep -> rep.setVerifiableCredentialsEnabled(true))); - } - - @DisplayName("Verify default values are correctly set") - @Test - public void testDefaultOid4VciClientScopeAttributes() { - ClientScopeRepresentation clientScope = new ClientScopeRepresentation(); - clientScope.setName("test-client-scope"); - clientScope.setDescription("test-client-scope-description"); - clientScope.setProtocol(OID4VCIConstants.OID4VC_PROTOCOL); - clientScope.setAttributes(Map.of("test-attribute", "test-value")); - - String clientScopeId = null; - try (Response response = clientScopes().create(clientScope)) { - Assertions.assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); - clientScopeId = ApiUtil.getCreatedId(response); - - ClientScopeRepresentation createdClientScope = clientScopes().get(clientScopeId).toRepresentation(); - Assertions.assertNotNull(createdClientScope); - Assertions.assertEquals(CredentialScopeModel.VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS_DEFAULT, - createdClientScope.getAttributes().get(CredentialScopeModel.VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS)); - Assertions.assertEquals(String.valueOf(CredentialScopeModel.VC_SD_JWT_NUMBER_OF_DECOYS_DEFAULT), - createdClientScope.getAttributes().get(CredentialScopeModel.VC_SD_JWT_NUMBER_OF_DECOYS)); - Assertions.assertEquals(CredentialScopeModel.VC_FORMAT_DEFAULT, - createdClientScope.getAttributes().get(CredentialScopeModel.VC_FORMAT)); - Assertions.assertEquals(CredentialScopeModel.VC_BUILD_CONFIG_HASH_ALGORITHM_DEFAULT, - createdClientScope.getAttributes().get(CredentialScopeModel.VC_BUILD_CONFIG_HASH_ALGORITHM)); - Assertions.assertEquals(CredentialScopeModel.VC_BUILD_CONFIG_TOKEN_JWS_TYPE_DEFAULT_SD_JWT_VC, - createdClientScope.getAttributes().get(CredentialScopeModel.VC_BUILD_CONFIG_TOKEN_JWS_TYPE)); - Assertions.assertEquals(String.valueOf(CredentialScopeModel.VC_EXPIRY_IN_SECONDS_DEFAULT), - createdClientScope.getAttributes().get(CredentialScopeModel.VC_EXPIRY_IN_SECONDS)); - Assertions.assertEquals(CredentialScopeModel.CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT, - createdClientScope.getAttributes().get(CredentialScopeModel.VC_CRYPTOGRAPHIC_BINDING_METHODS)); - Assertions.assertEquals(clientScope.getName(), - createdClientScope.getAttributes().get(CredentialScopeModel.VC_CONFIGURATION_ID)); - Assertions.assertEquals(clientScope.getName(), - createdClientScope.getAttributes().get(CredentialScopeModel.VC_IDENTIFIER)); - Assertions.assertEquals(clientScope.getName(), - createdClientScope.getAttributes().get(CredentialScopeModel.VC_SUPPORTED_TYPES)); - Assertions.assertEquals(clientScope.getName(), - createdClientScope.getAttributes().get(CredentialScopeModel.VC_CONTEXTS)); - Assertions.assertEquals(clientScope.getName(), - createdClientScope.getAttributes().get(CredentialScopeModel.VCT)); - // Note: ISSUER_DID is intentionally not set by default, as there's no sensible default - // The implementation leaves it undefined so the realm's URL will be used as the Issuer's ID - - } finally { - Assertions.assertNotNull(clientScopeId); - // cleanup - clientScopes().get(clientScopeId).remove(); - } - } - - @DisplayName("Verify CRUD of clientScope when OID4VCI is disabled for the realm") - @Test - public void testCreateOid4vciClientScopeDisabledForTheRealm() { - RealmRepresentation realm = managedRealm.admin().toRepresentation(); - try { - // Create clientScope1 successfully - String clientScopeId1; - ClientScopeRepresentation clientScope = new ClientScopeRepresentation(); - clientScope.setName("test-client-scope1"); - clientScope.setDescription("test-client-scope-description"); - clientScope.setProtocol(OID4VCIConstants.OID4VC_PROTOCOL); - clientScope.setAttributes(Map.of("test-attribute", "test-value")); - try (Response response = clientScopes().create(clientScope)) { - Assertions.assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); - clientScopeId1 = ApiUtil.getCreatedId(response); - } - - // Disable OID4VCI for the realm - realm.setVerifiableCredentialsEnabled(false); - managedRealm.admin().update(realm); - - // Test not possible to update existing oid4vci client-scope - ClientScopeResource clientScope1 = managedRealm.admin().clientScopes().get(clientScopeId1); - ClientScopeRepresentation clientScopeRep1 = clientScope1.toRepresentation(); - clientScopeRep1.setDescription("Foo"); - try { - clientScope1.update(clientScopeRep1); - Assert.fail("Not expected to update client scope"); - } catch (BadRequestException bre) { - // expected - } - - // Still possible to delete oid4vci clientScope - clientScope1.remove(); - - // Not possible to create new oid4vci clientScope - clientScope= new ClientScopeRepresentation(); - clientScope.setName("test-client-scope2"); - clientScope.setDescription("test-client-scope-description"); - clientScope.setProtocol(OID4VCIConstants.OID4VC_PROTOCOL); - clientScope.setAttributes(Map.of("test-attribute", "test-value")); - try (Response response = clientScopes().create(clientScope)) { - Assertions.assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); - } - } finally { - // Revert - realm.setVerifiableCredentialsEnabled(true); - managedRealm.admin().update(realm); - } - } - - public static class DefaultServerConfigWithOid4Vci implements KeycloakServerConfig { - - @Override - public KeycloakServerConfigBuilder configure(KeycloakServerConfigBuilder config) { - return config.features(Profile.Feature.OID4VC_VCI); - } - } -} diff --git a/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCClientScopeTest.java b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCClientScopeTest.java new file mode 100644 index 000000000000..8089a07220be --- /dev/null +++ b/tests/base/src/test/java/org/keycloak/tests/oid4vc/OID4VCClientScopeTest.java @@ -0,0 +1,236 @@ +/* + * Copyright 2025 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.tests.oid4vc; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import jakarta.ws.rs.BadRequestException; +import jakarta.ws.rs.core.Response; + +import org.keycloak.admin.client.resource.ClientScopeResource; +import org.keycloak.admin.client.resource.ClientScopesResource; +import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.admin.client.resource.RealmsResource; +import org.keycloak.protocol.oid4vc.model.CredentialScopeRepresentation; +import org.keycloak.representations.idm.ClientScopeRepresentation; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.testframework.annotations.KeycloakIntegrationTest; +import org.keycloak.testframework.util.ApiUtil; + +import org.junit.Assert; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import static org.keycloak.VCFormat.JWT_VC; +import static org.keycloak.VCFormat.SD_JWT_VC; +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.CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VCT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BINDING_REQUIRED; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BINDING_REQUIRED_PROOF_TYPES; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_HASH_ALGORITHM; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_HASH_ALGORITHM_DEFAULT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS_DEFAULT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_TOKEN_JWS_TYPE; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_TOKEN_JWS_TYPE_DEFAULT_JWT_VC; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_BUILD_CONFIG_TOKEN_JWS_TYPE_DEFAULT_SD_JWT_VC; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_CONFIGURATION_ID; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_CONTEXTS; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_CRYPTOGRAPHIC_BINDING_METHODS; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_EXPIRY_IN_SECONDS; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_EXPIRY_IN_SECONDS_DEFAULT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_FORMAT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_FORMAT_DEFAULT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_INCLUDE_IN_METADATA; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_SD_JWT_NUMBER_OF_DECOYS; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_SD_JWT_NUMBER_OF_DECOYS_DEFAULT; +import static org.keycloak.models.oid4vci.CredentialScopeModel.VC_SUPPORTED_TYPES; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * @author Pascal Knüppel + */ +@KeycloakIntegrationTest(config = OID4VCIssuerTestBase.VCTestServerConfig.class) +public class OID4VCClientScopeTest extends OID4VCIssuerTestBase { + + @DisplayName("Verify default values are correctly set") + @Test + public void testCredentialScopeDefaultAttributes() { + + ClientScopeRepresentation clientScope = new ClientScopeRepresentation(); + clientScope.setName("test-client-scope"); + clientScope.setDescription("test-client-scope-description"); + clientScope.setProtocol(OID4VC_PROTOCOL); + clientScope.setAttributes(Map.of("test-attribute", "test-value")); + + String clientScopeId = null; + ClientScopesResource clientScopes = testRealm.admin().clientScopes(); + try (Response response = clientScopes.create(clientScope)) { + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + clientScopeId = ApiUtil.getCreatedId(response); + + clientScope = clientScopes.get(clientScopeId).toRepresentation(); + assertNotNull(clientScope); + + Map attrs = new HashMap<>(clientScope.getAttributes()); + assertEquals("test-value", attrs.remove("test-attribute")); + assertEquals("true", attrs.remove(INCLUDE_IN_TOKEN_SCOPE)); + + assertEquals(VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS_DEFAULT, attrs.remove(VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS)); + assertEquals(String.valueOf(VC_SD_JWT_NUMBER_OF_DECOYS_DEFAULT), attrs.remove(VC_SD_JWT_NUMBER_OF_DECOYS)); + assertEquals(VC_FORMAT_DEFAULT, attrs.remove(VC_FORMAT)); + assertEquals(VC_BUILD_CONFIG_HASH_ALGORITHM_DEFAULT, attrs.remove(VC_BUILD_CONFIG_HASH_ALGORITHM)); + assertEquals(VC_BUILD_CONFIG_TOKEN_JWS_TYPE_DEFAULT_SD_JWT_VC, attrs.remove(VC_BUILD_CONFIG_TOKEN_JWS_TYPE)); + assertEquals(String.valueOf(VC_EXPIRY_IN_SECONDS_DEFAULT), attrs.remove(VC_EXPIRY_IN_SECONDS)); + assertEquals(CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT, attrs.remove(VC_CRYPTOGRAPHIC_BINDING_METHODS)); + assertEquals(clientScope.getName(), attrs.remove(VC_CONFIGURATION_ID)); + assertEquals(clientScope.getName(), attrs.remove(VC_SUPPORTED_TYPES)); + assertEquals(clientScope.getName(), attrs.remove(VC_CONTEXTS)); + assertEquals(clientScope.getName(), attrs.remove(VCT)); + assertEquals("true", attrs.remove(VC_INCLUDE_IN_METADATA)); + + assertEquals(Set.of(), attrs.keySet(), "Untested attributes"); + + // Note: ISSUER_DID is intentionally not set by default, as there's no sensible default + // The implementation leaves it undefined so the realm's URL will be used as the Issuer's ID + + } finally { + assertNotNull(clientScopeId); + clientScopes.get(clientScopeId).remove(); + } + } + + @DisplayName("Verify CRUD of clientScope when OID4VCI is disabled for the realm") + @Test + public void testCreateCredentialScopeForDisabledRealm() { + RealmRepresentation realm = testRealm.admin().toRepresentation(); + ClientScopesResource clientScopes = testRealm.admin().clientScopes(); + try { + // Create clientScope1 successfully + String clientScopeId; + ClientScopeRepresentation clientScopeRep = new ClientScopeRepresentation(); + clientScopeRep.setName("test-client-scope1"); + clientScopeRep.setDescription("test-client-scope-description"); + clientScopeRep.setProtocol(OID4VC_PROTOCOL); + clientScopeRep.setAttributes(Map.of("test-attribute", "test-value")); + try (Response response = clientScopes.create(clientScopeRep)) { + assertEquals(Response.Status.CREATED.getStatusCode(), response.getStatus()); + clientScopeId = ApiUtil.getCreatedId(response); + } + + // Disable OID4VCI for the realm + realm.setVerifiableCredentialsEnabled(false); + testRealm.admin().update(realm); + + // Test not possible to update existing oid4vci client-scope + ClientScopeResource clientScopeRes = clientScopes.get(clientScopeId); + clientScopeRep = clientScopeRes.toRepresentation(); + clientScopeRep.setDescription("Foo"); + try { + clientScopeRes.update(clientScopeRep); + Assert.fail("Not expected to update client scope"); + } catch (BadRequestException bre) { + // expected + } + + // Still possible to delete oid4vci clientScope + clientScopeRes.remove(); + + // Not possible to create new oid4vci clientScope + clientScopeRep = new ClientScopeRepresentation(); + clientScopeRep.setName("test-client-scope2"); + clientScopeRep.setDescription("test-client-scope-description"); + clientScopeRep.setProtocol(OID4VC_PROTOCOL); + clientScopeRep.setAttributes(Map.of("test-attribute", "test-value")); + try (Response response = clientScopes.create(clientScopeRep)) { + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + } finally { + // Revert + realm.setVerifiableCredentialsEnabled(true); + testRealm.admin().update(realm); + } + } + + @Test + public void testCreateRealmWithDefaultClientScopes() { + RealmsResource realms = keycloak.realms(); + String realmName = "aux-oid4vci-realm"; + try { + RealmRepresentation realmRep = new RealmRepresentation(); + realmRep.setRealm(realmName); + realmRep.setVerifiableCredentialsEnabled(true); + realms.create(realmRep); + + RealmResource realm = realms.realm(realmName); + realmRep = realm.toRepresentation(); + assertTrue(realmRep.isVerifiableCredentialsEnabled()); + + List clientScopes = realm.clientScopes().findAll().stream() + .filter(cs -> OID4VC_PROTOCOL.equals(cs.getProtocol())) + .map(CredentialScopeRepresentation::new) + .toList(); + + // expected: natural_person_jwt, natural_person_sd + assertEquals(2, clientScopes.size()); + + for (CredentialScopeRepresentation cs : clientScopes) { + Map attrs = new HashMap<>(cs.getAttributes()); + assertEquals("true", attrs.remove(INCLUDE_IN_TOKEN_SCOPE)); + assertEquals("true", attrs.remove(VC_INCLUDE_IN_METADATA)); + assertEquals("true", attrs.remove(VC_BINDING_REQUIRED)); + + assertEquals(VC_BUILD_CONFIG_HASH_ALGORITHM_DEFAULT, attrs.remove(VC_BUILD_CONFIG_HASH_ALGORITHM)); + assertEquals(String.valueOf(VC_EXPIRY_IN_SECONDS_DEFAULT), attrs.remove(VC_EXPIRY_IN_SECONDS)); + assertEquals(CRYPTOGRAPHIC_BINDING_METHODS_DEFAULT, attrs.remove(VC_CRYPTOGRAPHIC_BINDING_METHODS)); + assertEquals("jwt,attestation", attrs.remove(VC_BINDING_REQUIRED_PROOF_TYPES)); + assertEquals("oid4vc_natural_person", attrs.remove(VC_SUPPORTED_TYPES)); + assertEquals("oid4vc_natural_person", attrs.remove(VC_CONTEXTS)); + assertEquals("oid4vc_natural_person", attrs.remove(VCT)); + + switch (attrs.remove(VC_FORMAT)) { + case JWT_VC: { + assertEquals("oid4vc_natural_person_jwt", attrs.remove(VC_CONFIGURATION_ID)); + assertEquals(VC_BUILD_CONFIG_TOKEN_JWS_TYPE_DEFAULT_JWT_VC, attrs.remove(VC_BUILD_CONFIG_TOKEN_JWS_TYPE)); + break; + } + case SD_JWT_VC: { + assertEquals("oid4vc_natural_person_sd", attrs.remove(VC_CONFIGURATION_ID)); + assertEquals(VC_BUILD_CONFIG_TOKEN_JWS_TYPE_DEFAULT_SD_JWT_VC, attrs.remove(VC_BUILD_CONFIG_TOKEN_JWS_TYPE)); + assertEquals(VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS_DEFAULT, attrs.remove(VC_BUILD_CONFIG_SD_JWT_VISIBLE_CLAIMS)); + assertEquals(String.valueOf(VC_SD_JWT_NUMBER_OF_DECOYS_DEFAULT), attrs.remove(VC_SD_JWT_NUMBER_OF_DECOYS)); + break; + } + } + + assertEquals(Set.of(), attrs.keySet(), "Untested attributes"); + } + } finally { + realms.realm(realmName).remove(); + } + } +}