Repository navigation
Issues/48578 support for user attributes and updating them #49066
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
32b7025
bb8d74a
ae65c66
53e9434
946772c
fd9ea10
eb3236d
0acec61
ef9d774
d6957bb
3f05620
3d3c0e0
0163358
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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.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 UserVerifiableCredentialModel addVerifiableCredential(String userId, User | |
| 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 Stream<UserVerifiableCredentialModel> getVerifiableCredential 10000 sByUser(Stri | |
| .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() | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems to me that this could be simplified further to call just: without a need to manually create |
||
| .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; | ||
| } | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1042,6 +1042,7 @@ public static UserVerifiableCredentialRepresentation toRepresentation(UserVerifi | |
| rep.setCredentialScopeName(model.getCredentialScopeName()); | ||
| rep.setRevision(model.getRevision()); | ||
| rep.setCreatedDate(model.getCreatedDate()); | ||
| rep.setUserAttributes(model.getUserAttributes()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think similar change might be needed in the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks for the comment, I have extended RepresentationToModel for User attributes and modified test in OID4VCExportImportTest to test import and export of user attributes |
||
| return rep; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I’m not sure if it would be better to use a different name for this API and keep the PUT on /credentials for generic updates since we’ll probably need it to update other things like the revision. @mposolda WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me, the
updateCredentialworks. Maybe there is better name, but don't have better ideas.For example: Is
refreshCredentialbetter name or is it rather more confusing? To me, it is rather more confusing (as it can imply it has something to do with "refresh tokens" and "refreshing verifiable credentials" as described in the OID4VCI specs, which is not the case)Another name like
regenerateCredentialalso might be confusing IMO...So my vote is to likely keep as it is.