diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index 2ca00e789c..5ea1a8205a 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -125,13 +125,17 @@ public class JpaUserProvider implements UserProvider { @Override public void preRemove(RealmModel realm) { RealmEntity realmEntity = em.getReference(RealmEntity.class, realm.getId()); - int num = em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " mapping where mapping.user IN (select u from UserEntity u where realm=:realm)") + int num = em.createNamedQuery("deleteUserRoleMappingsByRealm") .setParameter("realm", realmEntity).executeUpdate(); - num = em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " socialLink where socialLink.user IN (select u from UserEntity u where realm=:realm)") + num = em.createNamedQuery("deleteSocialLinkByRealm") .setParameter("realm", realmEntity).executeUpdate(); - num = em.createQuery("delete from " + CredentialEntity.class.getSimpleName() + " mapping where mapping.user IN (select u from UserEntity u where realm=:realm)") + num = em.createNamedQuery("deleteCredentialsByRealm") .setParameter("realm", realmEntity).executeUpdate(); - num = em.createQuery("delete from UserEntity u where u.realm = :realm") + num = em.createNamedQuery("deleteUserAttributesByRealm") + .setParameter("realm", realmEntity).executeUpdate(); + num = em.createNamedQuery("deleteAuthenticationLinksByRealm") + .setParameter("realm", realmEntity).executeUpdate(); + num = em.createNamedQuery("deleteUsersByRealm") .setParameter("realm", realmEntity).executeUpdate(); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index fc2fcbcc0a..9543604401 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -12,7 +12,9 @@ import org.keycloak.models.UserModel; import org.keycloak.models.jpa.entities.AuthenticationLinkEntity; import org.keycloak.models.jpa.entities.CredentialEntity; import org.keycloak.models.jpa.entities.RoleEntity; +import org.keycloak.models.jpa.entities.UserAttributeEntity; import org.keycloak.models.jpa.entities.UserEntity; +import org.keycloak.models.jpa.entities.UserRequiredActionEntity; import org.keycloak.models.jpa.entities.UserRoleMappingEntity; import org.keycloak.models.utils.Pbkdf2PasswordEncoder; @@ -21,6 +23,7 @@ import javax.persistence.TypedQuery; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; @@ -79,52 +82,84 @@ public class UserAdapter implements UserModel { @Override public void setAttribute(String name, String value) { - Map attributes = user.getAttributes(); - if (attributes == null) { - attributes = new HashMap(); + for (UserAttributeEntity attr : user.getAttributes()) { + if (attr.getName().equals(name)) { + attr.setValue(value); + return; + } } - attributes.put(name, value); - user.setAttributes(attributes); + UserAttributeEntity attr = new UserAttributeEntity(); + attr.setName(name); + attr.setValue(value); + attr.setUser(user); + em.persist(attr); + user.getAttributes().add(attr); } @Override public void removeAttribute(String name) { - Map attributes = user.getAttributes(); - if (attributes == null) { - attributes = new HashMap(); + Iterator it = user.getAttributes().iterator(); + while (it.hasNext()) { + UserAttributeEntity attr = it.next(); + if (attr.getName().equals(name)) { + it.remove(); + em.remove(attr); + } } - attributes.remove(name); - user.setAttributes(attributes); } @Override public String getAttribute(String name) { - if (user.getAttributes() == null) return null; - return user.getAttributes().get(name); + for (UserAttributeEntity attr : user.getAttributes()) { + if (attr.getName().equals(name)) { + return attr.getValue(); + } + } + return null; } @Override public Map getAttributes() { Map result = new HashMap(); - result.putAll(user.getAttributes()); + for (UserAttributeEntity attr : user.getAttributes()) { + result.put(attr.getName(), attr.getValue()); + } return result; } @Override public Set getRequiredActions() { Set result = new HashSet(); - result.addAll(user.getRequiredActions()); + for (UserRequiredActionEntity attr : user.getRequiredActions()) { + result.add(attr.getAction()); + } return result; } @Override public void addRequiredAction(RequiredAction action) { - user.getRequiredActions().add(action); + for (UserRequiredActionEntity attr : user.getRequiredActions()) { + if (attr.getAction().equals(action)) { + return; + } + } + UserRequiredActionEntity attr = new UserRequiredActionEntity(); + attr.setAction(action); + attr.setUser(user); + em.persist(attr); + user.getRequiredActions().add(attr); } @Override public void removeRequiredAction(RequiredAction action) { - user.getRequiredActions().remove(action); + Iterator it = user.getRequiredActions().iterator(); + while (it.hasNext()) { + UserRequiredActionEntity attr = it.next(); + if (attr.getAction().equals(action)) { + it.remove(); + em.remove(attr); + } + } } @@ -357,10 +392,17 @@ public class UserAdapter implements UserModel { AuthenticationLinkEntity entity = new AuthenticationLinkEntity(); entity.setAuthProvider(authenticationLink.getAuthProvider()); entity.setAuthUserId(authenticationLink.getAuthUserId()); + entity.setUser(user); - user.setAuthenticationLink(entity); + if (user.getAuthenticationLink() != null) { + AuthenticationLinkEntity old = user.getAuthenticationLink(); + old.setUser(null); + em.remove(old); + user.setAuthenticationLink(null); + em.flush(); + } em.persist(entity); - em.persist(user); + user.setAuthenticationLink(entity); em.flush(); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java index 3a0aac1d8b..d4ae6e7bb3 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationLinkEntity.java @@ -2,14 +2,23 @@ package org.keycloak.models.jpa.entities; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.OneToOne; import org.hibernate.annotations.GenericGenerator; /** * @author Marek Posolda */ +@NamedQueries({ + @NamedQuery(name="deleteAuthenticationLinksByRealm", query="delete from AuthenticationLinkEntity authLink where authLink.user IN (select u from UserEntity u where realm=:realm)") +}) @Entity public class AuthenticationLinkEntity { @@ -20,6 +29,14 @@ public class AuthenticationLinkEntity { protected String authProvider; protected String authUserId; + // NOTE: @OnetoOne creates a constraint race condition if the join column is on AuthenticationLinkEntity. + // The race is that user gets loaded concurrently, creates link concurrently, and sets it. Therefore, we have + // a @ManyToOne on both sides. Broken yes, but, I think we're going to replace AuthenticationLinkEntity anyways. + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="userId") + protected UserEntity user; + + public long getId() { return id; } @@ -43,4 +60,12 @@ public class AuthenticationLinkEntity { public void setAuthUserId(String authUserId) { this.authUserId = authUserId; } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java index cc4c195868..2592cf218e 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/CredentialEntity.java @@ -17,7 +17,9 @@ import org.hibernate.annotations.GenericGenerator; * @version $Revision: 1 $ */ @NamedQueries({ - @NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type") + @NamedQuery(name="credentialByUserAndType", query="select cred from CredentialEntity cred where cred.user = :user and cred.type = :type"), + @NamedQuery(name="deleteCredentialsByRealm", query="delete from CredentialEntity cred where cred.user IN (select u from UserEntity u where realm=:realm)") + }) @Entity public class CredentialEntity { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java index a669aee569..b3fe6e6096 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/SocialLinkEntity.java @@ -5,6 +5,7 @@ import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; +import javax.persistence.JoinColumn; import javax.persistence.ManyToOne; import javax.persistence.NamedQueries; import javax.persistence.NamedQuery; @@ -18,7 +19,8 @@ import org.hibernate.annotations.GenericGenerator; @NamedQueries({ @NamedQuery(name="findSocialLinkByUser", query="select link from SocialLinkEntity link where link.user = :user"), @NamedQuery(name="findSocialLinkByUserAndProvider", query="select link from SocialLinkEntity link where link.user = :user and link.socialProvider = :socialProvider"), - @NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realm = :realm and link.socialProvider = :socialProvider and link.socialUserId = :socialUserId") + @NamedQuery(name="findUserByLinkAndRealm", query="select link.user from SocialLinkEntity link where link.realm = :realm and link.socialProvider = :socialProvider and link.socialUserId = :socialUserId"), + @NamedQuery(name="deleteSocialLinkByRealm", query="delete from SocialLinkEntity social where social.user IN (select u from UserEntity u where realm=:realm)") }) @Entity public class SocialLinkEntity { diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java new file mode 100755 index 0000000000..584dae0495 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserAttributeEntity.java @@ -0,0 +1,66 @@ +package org.keycloak.models.jpa.entities; + +import javax.persistence.EmbeddedId; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapsId; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import java.io.Serializable; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="deleteUserAttributesByRealm", query="delete from UserAttributeEntity attr where attr.user IN (select u from UserEntity u where realm=:realm)") +}) +@Entity +public class UserAttributeEntity { + @Id + @GeneratedValue + protected long id; + + @ManyToOne(fetch= FetchType.LAZY) + protected UserEntity user; + + protected String name; + protected String value; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java index e75d5b8b53..feb93ccf20 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java @@ -35,7 +35,8 @@ import java.util.Set; @NamedQuery(name="getRealmUserByUsername", query="select u from UserEntity u where u.username = :username and u.realm = :realm"), @NamedQuery(name="getRealmUserByEmail", query="select u from UserEntity u where u.email = :email and u.realm = :realm"), @NamedQuery(name="getRealmUserByLastName", query="select u from UserEntity u where u.lastName = :lastName and u.realm = :realm"), - @NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realm = :realm") + @NamedQuery(name="getRealmUserByFirstLastName", query="select u from UserEntity u where u.firstName = :first and u.lastName = :last and u.realm = :realm"), + @NamedQuery(name="deleteUsersByRealm", query="delete from UserEntity u where u.realm = :realm") }) @Entity @Table(uniqueConstraints = { @@ -62,20 +63,17 @@ public class UserEntity { @JoinColumn(name = "realm") protected RealmEntity realm; - @ElementCollection - @MapKeyColumn(name="name") - @Column(name="value") - @CollectionTable - protected Map attributes = new HashMap(); + @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") + protected Collection attributes = new ArrayList(); - @ElementCollection - @CollectionTable - protected Set requiredActions = new HashSet(); + @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") + protected Collection requiredActions = new ArrayList(); @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") protected Collection credentials = new ArrayList(); - @OneToOne(cascade = CascadeType.REMOVE, orphanRemoval = true) + @ManyToOne + @JoinColumn(name="link_id") protected AuthenticationLinkEntity authenticationLink; public String getId() { @@ -151,19 +149,19 @@ public class UserEntity { this.emailVerified = emailVerified; } - public Map getAttributes() { + public Collection getAttributes() { return attributes; } - public void setAttributes(Map attributes) { + public void setAttributes(Collection attributes) { this.attributes = attributes; } - public Set getRequiredActions() { + public Collection getRequiredActions() { return requiredActions; } - public void setRequiredActions(Set requiredActions) { + public void setRequiredActions(Collection requiredActions) { this.requiredActions = requiredActions; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java new file mode 100755 index 0000000000..21f2e5bcd1 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRequiredActionEntity.java @@ -0,0 +1,53 @@ +package org.keycloak.models.jpa.entities; + +import org.keycloak.models.UserModel; + +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +@NamedQueries({ + @NamedQuery(name="deleteUserRequiredActionsByRealm", query="delete from UserRequiredActionEntity action where action.user IN (select u from UserEntity u where realm=:realm)") +}) +@Entity +public class UserRequiredActionEntity { + @Id + @GeneratedValue + protected long id; + + @ManyToOne(fetch= FetchType.LAZY) + @JoinColumn(name="userId") + protected UserEntity user; + + protected UserModel.RequiredAction action; + + public long getId() { + return id; + } + + public UserModel.RequiredAction getAction() { + return action; + } + + public void setAction(UserModel.RequiredAction action) { + this.action = action; + } + + public UserEntity getUser() { + return user; + } + + public void setUser(UserEntity user) { + this.user = user; + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java index 0239616288..5cbe55b9a1 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserRoleMappingEntity.java @@ -19,7 +19,9 @@ import org.hibernate.annotations.GenericGenerator; @NamedQueries({ @NamedQuery(name="userHasRole", query="select m from UserRoleMappingEntity m where m.user = :user and m.role = :role"), @NamedQuery(name="userRoleMappings", query="select m from UserRoleMappingEntity m where m.user = :user"), - @NamedQuery(name="userRoleMappingIds", query="select m.role.id from UserRoleMappingEntity m where m.user = :user") + @NamedQuery(name="userRoleMappingIds", query="select m.role.id from UserRoleMappingEntity m where m.user = :user"), + @NamedQuery(name="deleteUserRoleMappingsByRealm", query="delete from UserRoleMappingEntity mapping where mapping.user IN (select u from UserEntity u where realm=:realm)") + }) @Entity public class UserRoleMappingEntity { diff --git a/model/jpa/src/test/resources/META-INF/persistence.xml b/model/jpa/src/test/resources/META-INF/persistence.xml index 7391d71910..8b3c6d7bba 100755 --- a/model/jpa/src/test/resources/META-INF/persistence.xml +++ b/model/jpa/src/test/resources/META-INF/persistence.xml @@ -15,6 +15,8 @@ org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserAttributeEntity + org.keycloak.models.jpa.entities.UserRequiredActionEntity org.keycloak.models.jpa.entities.UserRoleMappingEntity org.keycloak.models.jpa.entities.ScopeMappingEntity diff --git a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml index 294edb463d..f041f731b5 100755 --- a/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml +++ b/project-integrations/aerogear-ups/auth-server/src/main/resources/META-INF/persistence.xml @@ -14,6 +14,8 @@ org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserAttributeEntity + org.keycloak.models.jpa.entities.UserRequiredActionEntity org.keycloak.models.jpa.entities.UserRoleMappingEntity org.keycloak.models.jpa.entities.ScopeMappingEntity diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml index 294edb463d..feb9d2dc65 100755 --- a/server/src/main/resources/META-INF/persistence.xml +++ b/server/src/main/resources/META-INF/persistence.xml @@ -14,6 +14,8 @@ org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserRequiredActionEntity + org.keycloak.models.jpa.entities.UserAttributeEntity org.keycloak.models.jpa.entities.UserRoleMappingEntity org.keycloak.models.jpa.entities.ScopeMappingEntity diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml index 70544c6a93..9c04a330d5 100755 --- a/testsuite/integration/src/main/resources/META-INF/persistence.xml +++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml @@ -15,6 +15,8 @@ org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserAttributeEntity + org.keycloak.models.jpa.entities.UserRequiredActionEntity org.keycloak.models.jpa.entities.UserRoleMappingEntity org.keycloak.models.jpa.entities.ScopeMappingEntity diff --git a/testsuite/performance/src/test/resources/META-INF/persistence.xml b/testsuite/performance/src/test/resources/META-INF/persistence.xml index 87857f0e39..4850a25d5f 100755 --- a/testsuite/performance/src/test/resources/META-INF/persistence.xml +++ b/testsuite/performance/src/test/resources/META-INF/persistence.xml @@ -15,6 +15,8 @@ org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserAttributeEntity + org.keycloak.models.jpa.entities.UserRequiredActionEntity org.keycloak.models.jpa.entities.UserSessionEntity org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity org.keycloak.models.jpa.entities.UsernameLoginFailureEntity diff --git a/testsuite/tools/src/main/resources/META-INF/persistence.xml b/testsuite/tools/src/main/resources/META-INF/persistence.xml index ee721fe8be..4e3d279669 100755 --- a/testsuite/tools/src/main/resources/META-INF/persistence.xml +++ b/testsuite/tools/src/main/resources/META-INF/persistence.xml @@ -14,6 +14,8 @@ org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserAttributeEntity + org.keycloak.models.jpa.entities.UserRequiredActionEntity org.keycloak.models.jpa.entities.UserSessionEntity org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity org.keycloak.models.jpa.entities.UsernameLoginFailureEntity