mirror of
https://github.com/keycloak/keycloak.git
synced 2026-05-28 04:13:22 -04:00
Support for user attributes and updating them (#49066)
Closes #48578 Signed-off-by: Jimmy Chakkalakal <jimmy.chakkalakal@ibm.com>
This commit is contained in:
parent
27262be569
commit
5778a322fc
15 changed files with 246 additions and 6 deletions
|
|
@ -1,12 +1,16 @@
|
|||
package org.keycloak.representations.idm.oid4vc;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
|
||||
public class UserVerifiableCredentialRepresentation {
|
||||
|
||||
private String credentialScopeName;
|
||||
private String revision;
|
||||
private Long createdDate;
|
||||
private Map<String, List<String>> userAttributes;
|
||||
|
||||
public String getCredentialScopeName() {
|
||||
return credentialScopeName;
|
||||
|
|
@ -32,15 +36,22 @@ public class UserVerifiableCredentialRepresentation {
|
|||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getUserAttributes() { return userAttributes; }
|
||||
|
||||
public void setUserAttributes(Map<String, List<String>> userAttributes) { this.userAttributes = userAttributes; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
UserVerifiableCredentialRepresentation that = (UserVerifiableCredentialRepresentation) o;
|
||||
return Objects.equals(credentialScopeName, that.credentialScopeName) && Objects.equals(revision, that.revision) && Objects.equals(createdDate, that.createdDate);
|
||||
return Objects.equals(credentialScopeName, that.credentialScopeName)
|
||||
&& Objects.equals(revision, that.revision)
|
||||
&& Objects.equals(createdDate, that.createdDate)
|
||||
&& Objects.equals(userAttributes, that.userAttributes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(credentialScopeName, revision, createdDate);
|
||||
return Objects.hash(credentialScopeName, revision, createdDate, userAttributes);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import jakarta.ws.rs.Consumes;
|
|||
import jakarta.ws.rs.DELETE;
|
||||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
|
|
@ -34,5 +35,10 @@ public interface UserVerifiableCredentialResource {
|
|||
@Path("credentials/{credentialScopeName}")
|
||||
void revokeCredential(@PathParam("credentialScopeName") String credentialScopeName);
|
||||
|
||||
@PUT
|
||||
@Path("credentials/{credentialScopeName}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
UserVerifiableCredentialRepresentation updateCredential(@PathParam("credentialScopeName") String credentialScopeName);
|
||||
|
||||
// TODO: Issued credentials
|
||||
}
|
||||
|
|
|
|||
|
|
@ -870,6 +870,11 @@ public class UserCacheSession implements UserCache, OnCreateComponent, OnUpdateC
|
|||
return getDelegate().getVerifiableCredentialsByUser(userId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserVerifiableCredentialModel updateVerifiableCredential(String userId, String credentialScopeName) {
|
||||
return getDelegate().updateVerifiableCredential(userId, credentialScopeName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
|
||||
if (!isRegisteredForInvalidation(realm, user.getId())) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.models.jpa;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
|
|
@ -79,8 +80,11 @@ import org.keycloak.storage.StorageId;
|
|||
import org.keycloak.storage.UserStorageProvider;
|
||||
import org.keycloak.storage.client.ClientStorageProvider;
|
||||
import org.keycloak.storage.jpa.JpaHashUtils;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
import org.keycloak.utils.StringUtil;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import static org.keycloak.models.jpa.PaginationUtils.paginateQuery;
|
||||
import static org.keycloak.storage.jpa.JpaHashUtils.predicateForFilteringUsersByAttributes;
|
||||
import static org.keycloak.utils.StreamsUtil.closing;
|
||||
|
|
@ -382,6 +386,21 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
|
|||
vcEntity.setCreatedDate(createdDate);
|
||||
|
||||
vcEntity.setCredentialScopeName(verifCredentialModel.getCredentialScopeName());
|
||||
|
||||
Map<String, List<String>> userAttributes;
|
||||
if (verifCredentialModel.getUserAttributes() != null) {
|
||||
userAttributes = verifCredentialModel.getUserAttributes();
|
||||
} else {
|
||||
UserModel user = getUserById(session.getContext().getRealm(), userId);
|
||||
userAttributes = user.getAttributes();
|
||||
}
|
||||
|
||||
try {
|
||||
String attributesJson = JsonSerialization.writeValueAsString(userAttributes);
|
||||
vcEntity.setUserAttributes(attributesJson);
|
||||
} catch (IOException e) {
|
||||
throw new ModelException("Failed to serialize user attributes", e);
|
||||
}
|
||||
em.persist(vcEntity);
|
||||
em.flush();
|
||||
|
||||
|
|
@ -409,16 +428,63 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore, JpaUs
|
|||
.sorted(Comparator.comparing(UserVerifiableCredentialModel::getCredentialScopeName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserVerifiableCredentialModel updateVerifiableCredential(String userId, String credentialScopeName) {
|
||||
UserEntity userEntity = em.find(UserEntity.class, userId);
|
||||
if (userEntity == null || !session.getContext().getRealm().getId().equals(userEntity.getRealmId())) {
|
||||
throw new ModelException("User not found: " + userId);
|
||||
}
|
||||
UserModel user = new UserAdapter(session, session.getContext().getRealm(), em, userEntity);
|
||||
TypedQuery<UserVerifiableCredentialEntity> query = getVerifiableCredentialsEntitiesByUser();
|
||||
query.setParameter("userId", userId);
|
||||
|
||||
UserVerifiableCredentialEntity entity = query.getResultStream()
|
||||
.filter(vc -> credentialScopeName.equals(vc.getCredentialScopeName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new ModelException(
|
||||
"Verifiable credential not found: " + credentialScopeName));
|
||||
|
||||
|
||||
Map<String, List<String>> userAttributes = user.getAttributes();
|
||||
try {
|
||||
String attributesJson = JsonSerialization.writeValueAsString(userAttributes);
|
||||
entity.setUserAttributes(attributesJson);
|
||||
} catch (IOException e) {
|
||||
throw new ModelException("Failed to serialize user attributes", e);
|
||||
}
|
||||
|
||||
String newRevision = SecretGenerator.getInstance().generateSecureID();
|
||||
entity.setRevision(newRevision);
|
||||
UserVerifiableCredentialEntity mergedEntity = em.merge(entity);
|
||||
em.flush();
|
||||
|
||||
return toVerifiableCredentialModel(mergedEntity);
|
||||
}
|
||||
|
||||
private Stream<UserVerifiableCredentialEntity> getVerifiableCredentialsEntitiesByUser(String userId) {
|
||||
TypedQuery<UserVerifiableCredentialEntity> query = em.createNamedQuery("verifiableCredentialsByUser", UserVerifiableCredentialEntity.class);
|
||||
TypedQuery<UserVerifiableCredentialEntity> query = getVerifiableCredentialsEntitiesByUser();
|
||||
query.setParameter("userId", userId);
|
||||
return closing(query.getResultStream());
|
||||
}
|
||||
|
||||
private TypedQuery<UserVerifiableCredentialEntity> getVerifiableCredentialsEntitiesByUser() {
|
||||
return em.createNamedQuery("verifiableCredentialsByUser", UserVerifiableCredentialEntity.class);
|
||||
}
|
||||
|
||||
private UserVerifiableCredentialModel toVerifiableCredentialModel(UserVerifiableCredentialEntity entity) {
|
||||
UserVerifiableCredentialModel model = new UserVerifiableCredentialModel(entity.getCredentialScopeName());
|
||||
model.setRevision(entity.getRevision());
|
||||
model.setCreatedDate(entity.getCreatedDate());
|
||||
|
||||
if (entity.getUserAttributes() != null) {
|
||||
try {
|
||||
TypeReference<Map<String, List<String>>> typeRef = new TypeReference<>() {};
|
||||
Map<String, List<String>> attrs = JsonSerialization.readValue(entity.getUserAttributes(), typeRef);
|
||||
model.setUserAttributes(attrs);
|
||||
} catch (IOException e) {
|
||||
throw new ModelException("Failed to deserialize user attributes", e);
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,6 +40,9 @@ public class UserVerifiableCredentialEntity {
|
|||
@Column(name="REVISION")
|
||||
protected String revision;
|
||||
|
||||
@Column(name = "USER_ATTRIBUTES")
|
||||
protected String userAttributes;
|
||||
|
||||
@Column(name = "CREATED_DATE")
|
||||
private Long createdDate;
|
||||
|
||||
|
|
@ -83,6 +86,10 @@ public class UserVerifiableCredentialEntity {
|
|||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public String getUserAttributes() { return userAttributes; }
|
||||
|
||||
public void setUserAttributes(String userAttributes) { this.userAttributes = userAttributes; }
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
|||
|
|
@ -60,6 +60,9 @@
|
|||
<constraints nullable="false"/>
|
||||
</column>
|
||||
<column name="CREATED_DATE" type="BIGINT"/>
|
||||
<column name="USER_ATTRIBUTES" type="TEXT">
|
||||
<constraints nullable="true"/>
|
||||
</column>
|
||||
</createTable>
|
||||
<addPrimaryKey columnNames="ID" constraintName="CONSTRAINT_VCRED_PM" tableName="USER_VER_CREDENTIAL"/>
|
||||
<addForeignKeyConstraint baseColumnNames="USER_ID" baseTableName="USER_VER_CREDENTIAL" constraintName="FK_VCRED_USER" referencedColumnNames="ID" referencedTableName="USER_ENTITY"/>
|
||||
|
|
|
|||
|
|
@ -940,6 +940,15 @@ public class UserStorageManager extends AbstractStorageManager<UserStorageProvid
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public UserVerifiableCredentialModel updateVerifiableCredential(String userId, String credentialScopeName) {
|
||||
if (StorageId.isLocalStorage(userId)) {
|
||||
return localStorage().updateVerifiableCredential(userId, credentialScopeName);
|
||||
} else {
|
||||
throw new UnsupportedOperationException("Verifiable credential operations not yet supported on federated users");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeVerifiableCredential(String userId, String credentialScopeName) {
|
||||
if (StorageId.isLocalStorage(userId)) {
|
||||
|
|
|
|||
|
|
@ -1042,6 +1042,7 @@ public class ModelToRepresentation {
|
|||
rep.setCredentialScopeName(model.getCredentialScopeName());
|
||||
rep.setRevision(model.getRevision());
|
||||
rep.setCreatedDate(model.getCreatedDate());
|
||||
rep.setUserAttributes(model.getUserAttributes());
|
||||
return rep;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -998,6 +998,7 @@ public class RepresentationToModel {
|
|||
UserVerifiableCredentialModel verifCredentialModel = new UserVerifiableCredentialModel(rep.getCredentialScopeName());
|
||||
verifCredentialModel.setRevision(rep.getRevision());
|
||||
verifCredentialModel.setCreatedDate(rep.getCreatedDate());
|
||||
verifCredentialModel.setUserAttributes(rep.getUserAttributes());
|
||||
return verifCredentialModel;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -183,6 +183,16 @@ public interface UserProvider extends Provider,
|
|||
*/
|
||||
Stream<UserVerifiableCredentialModel> getVerifiableCredentialsByUser(String userId);
|
||||
|
||||
/**
|
||||
* Update verifiable credential by refreshing user attributes snapshot and incrementing revision
|
||||
*
|
||||
* @param userId id of the user
|
||||
* @param credentialScopeName credential scope name to update
|
||||
* @return updated credential model
|
||||
* @throws ModelException if credential doesn't exist
|
||||
*/
|
||||
UserVerifiableCredentialModel updateVerifiableCredential(String userId, String credentialScopeName);
|
||||
|
||||
/* FEDERATED IDENTITIES methods */
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,10 +1,14 @@
|
|||
package org.keycloak.models;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class UserVerifiableCredentialModel {
|
||||
|
||||
private final String credentialScopeName;
|
||||
private String revision;
|
||||
private Long createdDate;
|
||||
private Map<String, List<String>> userAttributes;
|
||||
|
||||
public UserVerifiableCredentialModel(String credentialScopeName) {
|
||||
this.credentialScopeName = credentialScopeName;
|
||||
|
|
@ -30,5 +34,7 @@ public class UserVerifiableCredentialModel {
|
|||
this.createdDate = createdDate;
|
||||
}
|
||||
|
||||
public Map<String, List<String>> getUserAttributes() { return userAttributes; }
|
||||
|
||||
public void setUserAttributes(Map<String, List<String>> userAttributes) { this.userAttributes = userAttributes; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import jakarta.ws.rs.DELETE;
|
|||
import jakarta.ws.rs.GET;
|
||||
import jakarta.ws.rs.NotFoundException;
|
||||
import jakarta.ws.rs.POST;
|
||||
import jakarta.ws.rs.PUT;
|
||||
import jakarta.ws.rs.Path;
|
||||
import jakarta.ws.rs.PathParam;
|
||||
import jakarta.ws.rs.Produces;
|
||||
|
|
@ -130,6 +131,41 @@ public class UserVerifiableCredentialResource {
|
|||
.toList();
|
||||
}
|
||||
|
||||
@PUT
|
||||
@Path("credentials/{credentialScopeName}")
|
||||
@Produces(MediaType.APPLICATION_JSON)
|
||||
@NoCache
|
||||
@Tag(name = KeycloakOpenAPI.Admin.Tags.USERS)
|
||||
@Operation(summary = "Update verifiable credential - refreshes user attributes snapshot and increments revision")
|
||||
@APIResponses(value = {
|
||||
@APIResponse(responseCode = "200", description = "OK", content = @Content(schema = @Schema(implementation = UserVerifiableCredentialRepresentation.class))),
|
||||
@APIResponse(responseCode = "400", description = "Bad request", content = @Content(schema = @Schema(implementation = ErrorRepresentation.class))),
|
||||
@APIResponse(responseCode = "403", description = "Forbidden"),
|
||||
@APIResponse(responseCode = "404", description = "Not Found")
|
||||
})
|
||||
public UserVerifiableCredentialRepresentation updateCredential(@PathParam("credentialScopeName") String credentialScopeName) {
|
||||
auth.users().requireManage(user);
|
||||
checkOid4VCIEnabled();
|
||||
|
||||
try {
|
||||
UserVerifiableCredentialModel updatedModel = session.users().updateVerifiableCredential(user.getId(), credentialScopeName);
|
||||
|
||||
UserVerifiableCredentialRepresentation updatedRep = ModelToRepresentation.toRepresentation(updatedModel);
|
||||
|
||||
adminEvent.operation(OperationType.UPDATE)
|
||||
.resourcePath(session.getContext().getUri(), credentialScopeName)
|
||||
.representation(updatedRep)
|
||||
.success();
|
||||
|
||||
return updatedRep;
|
||||
|
||||
} catch (ModelException e) {
|
||||
logger.warn(String.format("Verifiable credential '%s' not found for user '%s' in the realm '%s'.",
|
||||
credentialScopeName, user.getUsername(), realm.getName()));
|
||||
throw new NotFoundException("Verifiable credential not found");
|
||||
}
|
||||
}
|
||||
|
||||
@DELETE
|
||||
@Path("credentials/{credentialScopeName}")
|
||||
@Operation(summary = "Revoke verifiable credential for particular user")
|
||||
|
|
|
|||
|
|
@ -431,6 +431,7 @@ public class PermissionsTest extends AbstractPermissionsTest {
|
|||
UserVerifiableCredentialRepresentation verifCred = new UserVerifiableCredentialRepresentation();
|
||||
verifCred.setCredentialScopeName("nosuch");
|
||||
invoke(realm -> realm.users().get(user.getId()).verifiableCredentials().createCredential(verifCred), Resource.USER, true);
|
||||
invoke(realm -> realm.users().get(user.getId()).verifiableCredentials().updateCredential("nosuch"), Resource.USER, true);
|
||||
invoke(realm -> realm.users().get(user.getId()).verifiableCredentials().revokeCredential("nosuch"), Resource.USER, true);
|
||||
|
||||
invoke(realm -> realm.users().get(user.getId()).logout(), Resource.USER, true);
|
||||
|
|
|
|||
|
|
@ -38,7 +38,10 @@ import org.junit.jupiter.api.Test;
|
|||
import static org.keycloak.tests.oid4vc.OID4VCIssuerTestBase.jwtTypeNaturalPersonScopeName;
|
||||
import static org.keycloak.tests.oid4vc.OID4VCIssuerTestBase.sdJwtTypeNaturalPersonScopeName;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
@KeycloakIntegrationTest(config = OID4VCIssuerTestBase.VCTestServerConfig.class)
|
||||
|
|
@ -212,7 +215,60 @@ public class UserVerifiableCredentialsTest extends AbstractUserTest {
|
|||
}
|
||||
}
|
||||
|
||||
private void createVerifiableCedential(UserVerifiableCredentialResource user, String userId, String clientScopeName) {
|
||||
@Test
|
||||
@DatabaseTest
|
||||
public void verifyUpdateCredentialRefreshesAttributes() {
|
||||
String userId = createUser();
|
||||
UserResource userResource = managedRealm.admin().users().get(userId);
|
||||
UserVerifiableCredentialResource credResource = userResource.verifiableCredentials();
|
||||
|
||||
UserRepresentation user = userResource.toRepresentation();
|
||||
user.setFirstName("John");
|
||||
user.setLastName("Doe");
|
||||
user.setEmail("john.doe@example.com");
|
||||
userResource.update(user);
|
||||
adminEvents.clear();
|
||||
|
||||
UserVerifiableCredentialRepresentation created = createVerifiableCedential(credResource, userId, SCOPE_1_NAME);
|
||||
|
||||
String originalRevision = created.getRevision();
|
||||
assertNotNull(created.getUserAttributes(), "Initial snapshot should have attributes");
|
||||
assertEquals("John", created.getUserAttributes().get("firstName").get(0), "Initial firstName");
|
||||
assertEquals("Doe", created.getUserAttributes().get("lastName").get(0), "Initial lastName");
|
||||
assertEquals("john.doe@example.com", created.getUserAttributes().get("email").get(0), "Initial email");
|
||||
|
||||
|
||||
user = userResource.toRepresentation();
|
||||
user.setFirstName("Jane");
|
||||
user.setEmail("jane.doe@example.com");
|
||||
userResource.update(user);
|
||||
|
||||
UserVerifiableCredentialRepresentation updated = credResource.updateCredential(SCOPE_1_NAME);
|
||||
|
||||
assertNotNull(updated.getUserAttributes(), "Credential should have user attributes snapshot");
|
||||
|
||||
assertAll("Credential snapshot should reflect current user attributes",
|
||||
() -> assertEquals("Jane", updated.getUserAttributes().get("firstName").get(0),
|
||||
"firstName should be updated to Jane in snapshot"),
|
||||
() -> assertEquals("Doe", updated.getUserAttributes().get("lastName").get(0),
|
||||
"lastName should remain Doe in snapshot"),
|
||||
() -> assertEquals("jane.doe@example.com", updated.getUserAttributes().get("email").get(0),
|
||||
"email should be updated in snapshot"),
|
||||
() -> assertNotEquals(originalRevision, updated.getRevision(), "Revision should be updated")
|
||||
);
|
||||
|
||||
List<UserVerifiableCredentialRepresentation> all = credResource.getCredentials();
|
||||
UserVerifiableCredentialRepresentation retrieved = all.stream()
|
||||
.filter(c -> SCOPE_1_NAME.equals(c.getCredentialScopeName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new AssertionError("Credential not found"));
|
||||
|
||||
assertEquals(updated.getRevision(), retrieved.getRevision(), "Retrieved revision should match");
|
||||
assertEquals("Jane", retrieved.getUserAttributes().get("firstName").get(0), "Retrieved snapshot should have updated firstName");
|
||||
assertEquals("jane.doe@example.com", retrieved.getUserAttributes().get("email").get(0), "Retrieved snapshot should have updated email");
|
||||
}
|
||||
|
||||
private UserVerifiableCredentialRepresentation createVerifiableCedential(UserVerifiableCredentialResource user, String userId, String clientScopeName) {
|
||||
UserVerifiableCredentialRepresentation verifCred = new UserVerifiableCredentialRepresentation();
|
||||
verifCred.setCredentialScopeName(clientScopeName);
|
||||
UserVerifiableCredentialRepresentation createdRep = user.createCredential(verifCred);
|
||||
|
|
@ -221,6 +277,7 @@ public class UserVerifiableCredentialsTest extends AbstractUserTest {
|
|||
Assert.assertNotNull(createdRep.getCreatedDate());
|
||||
Assert.assertNotNull(createdRep.getRevision());
|
||||
AdminEventAssertion.assertEvent(adminEvents.poll(), OperationType.CREATE, AdminEventPaths.userVerifiableCredentialsPath(userId), createdRep, ResourceType.USER);
|
||||
return createdRep;
|
||||
}
|
||||
|
||||
private void assertVerifiableCredentials(List<UserVerifiableCredentialRepresentation> creds, String... expectedCredentialNames) {
|
||||
|
|
|
|||
|
|
@ -64,6 +64,10 @@ public class OID4VCExportImportTest extends OID4VCIssuerTestBase {
|
|||
List<UserVerifiableCredentialRepresentation> verifiableCreds = testRealm.admin().users().get(john.getId()).verifiableCredentials().getCredentials();
|
||||
assertUserCredentials(verifiableCreds, jwtTypeCredentialScopeName, sdJwtTypeCredentialScopeName, minimalJwtTypeCredentialScopeName, jwtTypeNaturalPersonScopeName, sdJwtTypeNaturalPersonScopeName);
|
||||
|
||||
for (UserVerifiableCredentialRepresentation cred : verifiableCreds) {
|
||||
assertNotNull(cred.getUserAttributes(), "User attributes should be stored in verifiable credential " + cred.getCredentialScopeName());
|
||||
}
|
||||
|
||||
// Export realm
|
||||
exportRealm("oid4vc-test-realm.json");
|
||||
|
||||
|
|
@ -74,8 +78,25 @@ public class OID4VCExportImportTest extends OID4VCIssuerTestBase {
|
|||
// Import the realm. Verify same verifiable credentials
|
||||
importRealm("oid4vc-test-realm.json");
|
||||
assertRealmExists(true);
|
||||
List<UserVerifiableCredentialRepresentation> importedVerifiableCreds = testRealm.admin().users().get(john.getId()).verifiableCredentials().getCredentials();
|
||||
assertEquals(verifiableCreds, importedVerifiableCreds);
|
||||
|
||||
UserRepresentation userAfterImport = testRealm.admin().users().search(TEST_USER).stream().findFirst().orElseThrow();
|
||||
List<UserVerifiableCredentialRepresentation> importedVerifiableCreds = testRealm.admin().users().get(userAfterImport.getId()).verifiableCredentials().getCredentials();
|
||||
|
||||
// Verify same number of credentials
|
||||
assertEquals(verifiableCreds.size(), importedVerifiableCreds.size());
|
||||
|
||||
// Verify each credential's userAttributes are preserved
|
||||
for (UserVerifiableCredentialRepresentation originalCred : verifiableCreds) {
|
||||
UserVerifiableCredentialRepresentation importedCred = importedVerifiableCreds.stream()
|
||||
.filter(c -> c.getCredentialScopeName().equals(originalCred.getCredentialScopeName()))
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("Credential scope not found after import: " + originalCred.getCredentialScopeName()));
|
||||
|
||||
assertNotNull(importedCred.getUserAttributes(), "Imported credential should have userAttributes");
|
||||
assertEquals(originalCred.getUserAttributes(), importedCred.getUserAttributes(),
|
||||
"User attributes in verifiable credential " + originalCred.getCredentialScopeName() + " should be preserved during export/import");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void assertUserCredentials(List<UserVerifiableCredentialRepresentation> userCreds, String... expectedCredentialNames) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue