8000 Issues/46204 update db schema and admin rest api by jimmychakkalakal · Pull Request #49201 · 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package org.keycloak.representations.idm.oid4vc;

import java.util.Objects;

public class IssuedVerifiableCredentialRepresentation {

private String id;
private String userId;
private String credentialType;
private Long issuedAt;
private Long expiresAt;
// This represents UUID of the client, which acts as OID4VCI wallet
private String clientId;
private String revision;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getUserId() {
return userId;
}

public void setUserId(String userId) {
this.userId = userId;
}

public String getCredentialType() {
return credentialType;
}

public void setCredentialType(String credentialType) {
this.credentialType = credentialType;
}

public Long getIssuedAt() {
return issuedAt;
}

public void setIssuedAt(Long issuedAt) {
this.issuedAt = issuedAt;
}

public Long getExpiresAt() {
return expiresAt;
}

public void setExpiresAt(Long expiresAt) {
this.expiresAt = expiresAt;
}

public String getClientId() {
return clientId;
}

public void setClientId(String clientId) {
this.clientId = clientId;
}

public String getRevision() {
return revision;
}

public void setRevision(String revision) {
this.revision = revision;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IssuedVerifiableCredentialRepresentation that = (IssuedVerifiableCredentialRepresentation) o;
return Objects.equals(id, that.id);
}

@Override
public int hashCode() {
return Objects.hash(id);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.keycloak.representations.idm.oid4vc.IssuedVerifiableCredentialRepresentation;
import org.keycloak.representations.idm.oid4vc.UserVerifiableCredentialRepresentation;

/**
Expand Down Expand Up @@ -40,5 +41,8 @@ public interface UserVerifiableCredentialResource {
@Produces(MediaType.APPLICATION_JSON)
UserVerifiableCredentialRepresentation updateCredential(@PathParam("credentialScopeName") String credentialScopeName);

// TODO: Issued credentials
@GET
@Path("issued-credentials")
@Produces(MediaType.APPLICATION_JSON)
List<IssuedVerifiableCredentialRepresentation> getIssuedCredentials();
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IssuedVerifiableCredentialModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakTransaction;
import org.keycloak.models.ProtocolMapperModel;
Expand Down Expand Up @@ -875,6 +876,16 @@ public UserVerifiableCredentialModel updateVerifiableCredential(String userId, S
return getDelegate().updateVerifiableCredential(userId, credentialScopeName);
}

@Override
public void addIssuedVerifiableCredential(IssuedVerifiableCredentialModel issuedVc) {
getDelegate().addIssuedVerifiableCredential(issuedVc);
}

@Override
public Stream<IssuedVerifiableCredentialModel> getIssuedVerifiableCredentialsStreamByUser(String userId) {
return getDelegate().getIssuedVerifiableCredentialsStreamByUser(userId);
}

@Override
public void setNotBeforeForUser(RealmModel realm, UserModel user, int notBefore) {
if (!isRegisteredForInvalidation(realm, user.getId())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.keycloak.models.FederatedIdentityModel;
import org.keycloak.models.GroupModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.IssuedVerifiableCredentialModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
Expand All @@ -68,6 +69,7 @@
import org.keycloak.models.UserVerifiableCredentialModel;
import org.keycloak.models.jpa.entities.CredentialEntity;
import org.keycloak.models.jpa.entities.FederatedIdentityEntity;
import org.keycloak.models.jpa.entities.IssuedVerifiableCredentialEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserConsentClientScopeEntity;
import org.keycloak.models.jpa.entities.UserConsentEntity;
Expand Down Expand Up @@ -170,6 +172,7 @@ private void removeUser(UserEntity user) {
em.createNamedQuery("deleteUserConsentClientScopesByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteUserConsentsByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteVerifiableCredentialsByUser").setParameter("user", user).executeUpdate();
em.createNamedQuery("deleteIssuedVcsByUser").setParameter("userId", user.getId()).executeUpdate();

em.remove(user);
em.flush();
Expand Down Expand Up @@ -518,6 +521,8 @@ public void preRemove(RealmModel realm) {
.setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("deleteVerifiableCredentialsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("deleteIssuedVcsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("deleteUserRoleMappingsByRealm")
.setParameter("realmId", realm.getId()).executeUpdate();
em.createNamedQuery("deleteUserRequiredActionsByRealm")
Expand Down Expand Up @@ -623,6 +628,9 @@ public void preRemove(ClientScopeModel clientScope) {
em.createNamedQuery("deleteVerifiableCredentialsByClientScope")
.setParameter("scopeName", clientScope.getName())
.executeUpdate();
em.createNamedQuery("deleteIssuedVcsByClientScope")
.setParameter("scopeName", clientScope.getName())
.executeUpdate();
}

@Override
Expand Down Expand Up @@ -1080,6 +1088,45 @@ public UserCredentialManager getUserCredentialManager(UserModel user) {
return new org.keycloak.credential.UserCredentialManager(session, session.getContext().getRealm(), user);
}

@Override
public void addIssuedVerifiableCredential(IssuedVerifiableCredentialModel model) {

String revision;
if (model.getRevision() != null) {
revision = model.getRevision();
} else {
UserVerifiableCredentialEntity userVerifiableCredentialEntity = getVerifiableCredentialsEntitiesByUser(model.getUserId())
.filter(vc -> model.getCredentialType().equals(vc.getCredentialScopeName()))
.findFirst()
.orElseThrow(() -> new ModelException(
"Verifiable credential not found: " + model.getCredentialType()));
revision = userVerifiableCredentialEntity.getRevision();
}

IssuedVerifiableCredentialEntity issuedVerifiableCredentialEntity = new IssuedVerifiableCredentialEntity();

issuedVerifiableCredentialEntity.setId(KeycloakModelUtils.generateId());
issuedVerifiableCredentialEntity.setUser(em.getReference(UserEntity.class, model.getUserId()));
issuedVerifiableCredentialEntity.setCredentialType(model.getCredentialType());
issuedVerifiableCredentialEntity.setClientId(model.getClientId());
issuedVerifiableCredentialEntity.setRevision(revision);

long issuedAt = model.getIssuedAt() != null ? model.getIssuedAt() : Time.currentTimeMillis();
issuedVerifiableCredentialEntity.setIssuedAt(issuedAt);

issuedVerifiableCredentialEntity.setExpiresAt(model.getExpiresAt());

em.persist(issuedVerifiableCredentialEntity);
em.flush();
}

@Override
public Stream<IssuedVerifiableCredentialModel> getIssuedVerifiableCredentialsStreamByUser(String userId) {
TypedQuery<IssuedVerifiableCredentialEntity> query = em.createNamedQuery("issuedVcsByUser", IssuedVerifiableCredentialEntity.class);
query.setParameter("userId", userId);
return closing(query.getResultStream()).map(this::toIssuedVcModel);
}

// Could override this to provide a custom behavior.
protected void ensureEmailConstraint(List<UserEntity> users, RealmModel realm) {
UserEntity user = users.get(0);
Expand Down Expand Up @@ -1260,4 +1307,17 @@ private static CriteriaQuery<Long> countQuery(CriteriaQuery<Long> query, Criteri
return query.select(from.getJoins().isEmpty() ? builder.count(from) : builder.countDistinct(from))
.where(predicates);
}

private IssuedVerifiableCredentialModel toIssuedVcModel(IssuedVerifiableCredentialEntity entity) {
IssuedVerifiableCredentialModel model = new IssuedVerifiableCredentialModel();
model.setId(entity.getId());
model.setUserId(entity.getUser().getId());
model.setCredentialType(entity.getCredentialType());
model.setRevision(entity.getRevision());
model.setIssuedAt(entity.getIssuedAt());
model.setExpiresAt(entity.getExpiresAt());
model.setClientId(entity.getClientId());

return model;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
package org.keycloak.models.jpa.entities;

import jakarta.persistence.Access;
import jakarta.persistence.AccessType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.NamedQueries;
import jakarta.persistence.NamedQuery;
import jakarta.persistence.Table;

@Entity
@Table(name="ISSUED_VER_CREDENTIAL")
@NamedQueries({
@NamedQuery(name="issuedVcsByUser", query="select vc from IssuedVerifiableCredentialEntity vc where vc.user.id = :userId order by vc.issuedAt desc"),
@NamedQuery(name="deleteIssuedVcsByRealm", query="delete from IssuedVerifiableCredentialEntity vc where vc.user IN (select u from UserEntity u where u.realmId = :realmId)"),
@NamedQuery(name="deleteIssuedVcsByUser", query="delete from IssuedVerifiableCredentialEntity vc where vc.user.id = :userId"),
@NamedQuery(name="deleteIssuedVcsByClientScope", query="delete from IssuedVerifiableCredentialEntity vc where vc.credentialType = :scopeName"),
})
public class IssuedVerifiableCredentialEntity {

@Id
@Column(name="ID", length = 36)
@Access(AccessType.PROPERTY)
protected String id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name="USER_ID")
protected UserEntity user;

@Column(name="CREDENTIAL_TYPE")
protected String credentialType;

@Column(name="ISSUED_AT")
protected Long issuedAt;

@Column(name="EXPIRES_AT")
protected Long expiresAt;

// This represents UUID of the client, which acts as OID4VCI wallet
@Column(name="CLIENT_ID")
protected String clientId;

@Column(name="REVISION")
protected String revision;

public void setId(String id) {
this.id = id;
}

public String getId() {
return id;
}

public UserEntity getUser() {
return user;
}

public void setUser(UserEntity user) {
this.user = user;
}

public String getCredentialType() {
return credentialType;
}

public void setCredentialType(String credentialType) {
this.credentialType = credentialType;
}

public Long getIssuedAt() {
return issuedAt;
}

public void setIssuedAt(Long issuedAt) {
this.issuedAt = issuedAt;
}

public Long getExpiresAt() {
return expiresAt;
}

public void setExpiresAt(Long expiresAt) {
this.expiresAt = expiresAt;
}

public String getClientId() {
return clientId;
}

public void setClientId(String clientId) {
this.clientId = clientId;
}

public String getRevision() {
return revision;
}

public void setRevision(String revision) {
this.revision = revision;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
IssuedVerifiableCredentialEntity that = (IssuedVerifiableCredentialEntity) o;
return id.equals(that.id);
}

@Override
public int hashCode() {
return id.hashCode();
}
}
35 changes: 35 additions & 0 deletions model/jpa/src/main/resources/META-INF/jpa-changelog-26.7.0.xml
Original file line number Diff line number Diff line change
Expand Up @@ -203,4 +203,39 @@

</changeSet>

<changeSet author="keycloak" id="26.7.0-46204-issued-ver-credential-table">
<createTable tableName="ISSUED_VER_CREDENTIAL">
<column name="ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="USER_ID" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
<column name="CREDENTIAL_TYPE" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="ISSUED_AT" type="BIGINT">
<constraints nullable="false"/>
</column>
<column name="EXPIRES_AT" type="BIGINT"/>
<column name="CLIENT_ID" type="VARCHAR(36)"/>
<column name="REVISION" type="VARCHAR(36)">
<constraints nullable="false"/>
</column>
</createTable>
<addPrimaryKey columnNames="ID" constraintName="PK_ISSUED_VER_CREDENTIAL" tableName="ISSUED_VER_CREDENTIAL"/>

<addForeignKeyConstraint baseColumnNames="USER_ID"
baseTableName="ISSUED_VER_CREDENTIAL"
constraintName="FK_ISSUED_VER_CREDENTIAL_USER"
referencedColumnNames="ID"
referencedTableName="USER_ENTITY"/>

<!-- Index for listing user's credentials -->
<createIndex tableName="ISSUED_VER_CREDENTIAL" indexName="IDX_ISSUED_VER_CREDENTIAL_USER">
<column name="USER_ID"/>
<column name="ISSUED_AT"/>
</createIndex>
</changeSet>

</databaseChangeLog>
Loading
Loading
0