keycloak-uncached
Changes
model/hybrid/pom.xml 30(+30 -0)
model/pom.xml 7(+7 -0)
model/realms-jpa/pom.xml 45(+45 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ApplicationEntity.java 77(+77 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/AuthenticationProviderEntity.java 79(+79 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ClientEntity.java 126(+126 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/OAuthClientEntity.java 27(+27 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RealmEntity.java 479(+479 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RequiredCredentialEntity.java 64(+64 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ScopeMappingEntity.java 60(+60 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaKeycloakTransaction.java 53(+53 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProviderFactory.java 41(+41 -0)
model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/PersistenceExceptionConverter.java 48(+48 -0)
model/realms-jpa/src/main/resources/META-INF/services/org.keycloak.models.realms.RealmProviderFactory 1(+1 -0)
model/sessions-jpa/pom.xml 45(+45 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionAssociationEntity.java 84(+84 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java 86(+86 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java 105(+105 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaKeycloakTransaction.java 53(+53 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProvider.java 155(+155 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProviderFactory.java 40(+40 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/PersistenceExceptionConverter.java 48(+48 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UsernameLoginFailureAdapter.java 69(+69 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java 122(+122 -0)
model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/utils/JpaIdGenerator.java 19(+19 -0)
model/sessions-jpa/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory 1(+1 -0)
model/sessions-mem/pom.xml 35(+35 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureAdapter.java 81(+81 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProvider.java 194(+194 -0)
model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProviderFactory.java 43(+43 -0)
model/sessions-mem/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory 1(+1 -0)
model/tests-hybrid/pom.xml 55(+55 -0)
model/users-jpa/pom.xml 79(+79 -0)
model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserAttributeEntity.java 79(+79 -0)
model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserCredentialEntity.java 107(+107 -0)
model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserRoleMappingEntity.java 68(+68 -0)
model/users-jpa/src/main/java/org/keycloak/models/users/jpa/PersistenceExceptionConverter.java 48(+48 -0)
model/users-jpa/src/main/resources/META-INF/services/org.keycloak.models.users.UserProviderFactory 1(+1 -0)
server/pom.xml 22(+21 -1)
testsuite/integration/pom.xml 22(+21 -1)
testsuite/performance/pom.xml 25(+25 -0)
testsuite/tools/pom.xml 20(+20 -0)
Details
model/hybrid/pom.xml 30(+30 -0)
diff --git a/model/hybrid/pom.xml b/model/hybrid/pom.xml
new file mode 100755
index 0000000..344fef3
--- /dev/null
+++ b/model/hybrid/pom.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <name>Keycloak Model Hybrid</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/ApplicationAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/ApplicationAdapter.java
new file mode 100644
index 0000000..57ff498
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/ApplicationAdapter.java
@@ -0,0 +1,140 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class ApplicationAdapter extends ClientAdapter implements ApplicationModel {
+
+ private Application application;
+
+ ApplicationAdapter(HybridModelProvider provider, Application application) {
+ super(provider, application);
+ this.application = application;
+ }
+
+ Application getApplication() {
+ return application;
+ }
+
+ @Override
+ public void updateApplication() {
+ application.updateApplication();
+ }
+
+ @Override
+ public String getName() {
+ return application.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ application.setName(name);
+ }
+
+ @Override
+ public boolean isSurrogateAuthRequired() {
+ return application.isSurrogateAuthRequired();
+ }
+
+ @Override
+ public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+ application.setSurrogateAuthRequired(surrogateAuthRequired);
+ }
+
+ @Override
+ public String getManagementUrl() {
+ return application.getManagementUrl();
+ }
+
+ @Override
+ public void setManagementUrl(String url) {
+ application.setManagementUrl(url);
+ }
+
+ @Override
+ public String getBaseUrl() {
+ return application.getBaseUrl();
+ }
+
+ @Override
+ public void setBaseUrl(String url) {
+ application.setBaseUrl(url);
+ }
+
+ @Override
+ public List<String> getDefaultRoles() {
+ return application.getDefaultRoles();
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ if (getRole(name) == null) {
+ addRole(name);
+ }
+
+ application.addDefaultRole(name);
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ application.updateDefaultRoles(defaultRoles);
+ }
+
+ @Override
+ public Set<RoleModel> getApplicationScopeMappings(ClientModel client) {
+ return provider.mappings().wrap(application.getApplicationScopeMappings(provider.mappings().unwrap(client)));
+ }
+
+ @Override
+ public boolean isBearerOnly() {
+ return application.isBearerOnly();
+ }
+
+ @Override
+ public void setBearerOnly(boolean only) {
+ application.setBearerOnly(only);
+ }
+
+ @Override
+ public RoleModel getRole(String name) {
+ return provider.mappings().wrap(application.getRole(name));
+ }
+
+ @Override
+ public RoleModel addRole(String name) {
+ return addRole(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public RoleModel addRole(String id, String name) {
+ return provider.mappings().wrap(application.addRole(id, name));
+ }
+
+ @Override
+ public boolean removeRole(RoleModel role) {
+ if (application.removeRole(provider.mappings().unwrap(role))) {
+ provider.users().onRoleRemoved(role.getId());
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public Set<RoleModel> getRoles() {
+ return provider.mappings().wrap(application.getRoles());
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/ClientAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/ClientAdapter.java
new file mode 100644
index 0000000..a35b662
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/ClientAdapter.java
@@ -0,0 +1,202 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.realms.Client;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class ClientAdapter implements ClientModel {
+
+ protected HybridModelProvider provider;
+
+ protected Client client;
+
+ ClientAdapter(HybridModelProvider provider, Client client) {
+ this.provider = provider;
+ this.client = client;
+ }
+
+ @Override
+ public String getId() {
+ return client.getId();
+ }
+
+ @Override
+ public String getClientId() {
+ return client.getClientId();
+ }
+
+ @Override
+ public long getAllowedClaimsMask() {
+ return client.getAllowedClaimsMask();
+ }
+
+ @Override
+ public void setAllowedClaimsMask(long mask) {
+ client.setAllowedClaimsMask(mask);
+ }
+
+ @Override
+ public Set<String> getWebOrigins() {
+ return client.getWebOrigins();
+ }
+
+ @Override
+ public void setWebOrigins(Set<String> webOrigins) {
+ client.setWebOrigins(webOrigins);
+ }
+
+ @Override
+ public void addWebOrigin(String webOrigin) {
+ client.addWebOrigin(webOrigin);
+ }
+
+ @Override
+ public void removeWebOrigin(String webOrigin) {
+ client.removeWebOrigin(webOrigin);
+ }
+
+ @Override
+ public Set<String> getRedirectUris() {
+ return client.getRedirectUris();
+ }
+
+ @Override
+ public void setRedirectUris(Set<String> redirectUris) {
+ client.setRedirectUris(redirectUris);
+ }
+
+ @Override
+ public void addRedirectUri(String redirectUri) {
+ client.addRedirectUri(redirectUri);
+ }
+
+ @Override
+ public void removeRedirectUri(String redirectUri) {
+ client.removeRedirectUri(redirectUri);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return client.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ client.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean validateSecret(String secret) {
+ return client.validateSecret(secret);
+ }
+
+ @Override
+ public String getSecret() {
+ return client.getSecret();
+ }
+
+ @Override
+ public void setSecret(String secret) {
+ client.setSecret(secret);
+ }
+
+ @Override
+ public boolean isPublicClient() {
+ return client.isPublicClient();
+ }
+
+ @Override
+ public void setPublicClient(boolean flag) {
+ client.setPublicClient(flag);
+ }
+
+ @Override
+ public boolean isDirectGrantsOnly() {
+ return client.isDirectGrantsOnly();
+ }
+
+ @Override
+ public void setDirectGrantsOnly(boolean flag) {
+ client.setDirectGrantsOnly(flag);
+ }
+
+ @Override
+ public Set<RoleModel> getScopeMappings() {
+ return provider.mappings().wrap(client.getScopeMappings());
+ }
+
+ @Override
+ public void addScopeMapping(RoleModel role) {
+ if (!hasScope(role)) {
+ client.addScopeMapping(provider.mappings().unwrap(role));
+ }
+ }
+
+ @Override
+ public void deleteScopeMapping(RoleModel role) {
+ client.deleteScopeMapping(provider.mappings().unwrap(role));
+ }
+
+ @Override
+ public Set<RoleModel> getRealmScopeMappings() {
+ return provider.mappings().wrap(client.getRealmScopeMappings());
+ }
+
+ @Override
+ public boolean hasScope(RoleModel role) {
+ Set<RoleModel> roles = getScopeMappings();
+ if (roles.contains(role)) {
+ return true;
+ }
+
+ for (RoleModel mapping : roles) {
+ if (mapping.hasRole(role)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ @Override
+ public RealmModel getRealm() {
+ return provider.mappings().wrap(client.getRealm());
+ }
+
+ @Override
+ public int getNotBefore() {
+ return client.getNotBefore();
+ }
+
+ @Override
+ public void setNotBefore(int notBefore) {
+ client.setNotBefore(notBefore);
+ }
+
+ @Override
+ public Set<UserSessionModel> getUserSessions() {
+ return provider.mappings().wrapSessions(getRealm(), provider.sessions().getUserSessionsByClient(client.getRealm().getId(), client.getId()));
+ }
+
+ @Override
+ public int getActiveUserSessions() {
+ return provider.sessions().getActiveUserSessions(client.getRealm().getId(), client.getId());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!this.getClass().equals(o.getClass())) return false;
+
+ ClientAdapter that = (ClientAdapter) o;
+ return that.getId().equals(getId());
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridKeycloakTransaction.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridKeycloakTransaction.java
new file mode 100644
index 0000000..b6a54ea
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridKeycloakTransaction.java
@@ -0,0 +1,65 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.KeycloakTransaction;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HybridKeycloakTransaction implements KeycloakTransaction {
+
+ private KeycloakTransaction[] txs;
+
+ public HybridKeycloakTransaction(KeycloakTransaction... txs) {
+ this.txs = txs;
+ }
+
+ @Override
+ public void begin() {
+ for (KeycloakTransaction tx : txs) {
+ tx.begin();
+ }
+ }
+
+ @Override
+ public void commit() {
+ // TODO What do we do if one tx fails?
+ for (KeycloakTransaction tx : txs) {
+ tx.commit();
+ }
+ }
+
+ @Override
+ public void rollback() {
+ for (KeycloakTransaction tx : txs) {
+ tx.rollback();
+ }
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ for (KeycloakTransaction tx : txs) {
+ tx.setRollbackOnly();
+ }
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ for (KeycloakTransaction tx : txs) {
+ if (tx.getRollbackOnly()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isActive() {
+ for (KeycloakTransaction tx : txs) {
+ if (tx.isActive()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridModelProvider.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridModelProvider.java
new file mode 100644
index 0000000..0a3d778
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridModelProvider.java
@@ -0,0 +1,256 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.ModelProvider;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.sessions.SessionProvider;
+import org.keycloak.models.users.UserProvider;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.util.Time;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HybridModelProvider implements ModelProvider {
+
+ private final Mappings mappings;
+
+ private KeycloakSession session;
+
+ private HybridKeycloakTransaction tx;
+
+ public HybridModelProvider(KeycloakSession session) {
+ this.session = session;
+ this.mappings = new Mappings(this);
+ }
+
+ @Override
+ public KeycloakTransaction getTransaction() {
+ if (tx == null) {
+ tx = new HybridKeycloakTransaction(realms().getTransaction(), users().getTransaction(), sessions().getTransaction());
+ }
+
+ return tx;
+ }
+
+ Mappings mappings() {
+ return mappings;
+ }
+
+ SessionProvider sessions() {
+ return session.getProvider(SessionProvider.class);
+ }
+
+ UserProvider users() {
+ return session.getProvider(UserProvider.class);
+ }
+
+ RealmProvider realms() {
+ return session.getProvider(RealmProvider.class);
+ }
+
+ @Override
+ public RealmModel createRealm(String name) {
+ return createRealm(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public RealmModel createRealm(String id, String name) {
+ return mappings.wrap(realms().createRealm(id, name));
+ }
+
+ @Override
+ public RealmModel getRealm(String id) {
+ return mappings.wrap(realms().getRealm(id));
+ }
+
+ @Override
+ public RealmModel getRealmByName(String name) {
+ return mappings.wrap(realms().getRealmByName(name));
+ }
+
+ @Override
+ public UserModel getUserById(String id, RealmModel realm) {
+ return mappings.wrap(realm, users().getUserById(id, realm.getId()));
+ }
+
+ @Override
+ public UserModel getUserByUsername(String username, RealmModel realm) {
+ return mappings.wrap(realm, users().getUserByUsername(username, realm.getId()));
+ }
+
+ @Override
+ public UserModel getUserByEmail(String email, RealmModel realm) {
+ return mappings.wrap(realm, users().getUserByEmail(email, realm.getId()));
+ }
+
+ @Override
+ public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
+ return mappings.wrap(realm, users().getUserByAttribute("keycloak.socialLink." + socialLink.getSocialProvider() + ".userId", socialLink.getSocialUserId(), realm.getId()));
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return mappings.wrapUsers(realm, users().getUsers(realm.getId()));
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search, RealmModel realm) {
+ return mappings.wrapUsers(realm, users().searchForUser(search, realm.getId()));
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+ return mappings.wrapUsers(realm, users().searchForUserByAttributes(attributes, realm.getId()));
+ }
+
+ @Override
+ public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
+ Set<SocialLinkModel> links = new HashSet<SocialLinkModel>();
+
+ for (Map.Entry<String, String> e : user.getAttributes().entrySet()) {
+ if (e.getKey().matches("keycloak\\.socialLink\\..*\\.userId")) {
+ String provider = e.getKey().split("\\.")[2];
+
+ SocialLinkModel link = new SocialLinkModel();
+ link.setSocialProvider(provider);
+ link.setSocialUserId(e.getValue());
+ link.setSocialUsername(user.getAttribute("keycloak.socialLink." + provider + ".username"));
+
+ links.add(link);
+ }
+
+ }
+
+ return links;
+ }
+
+ @Override
+ public SocialLinkModel getSocialLink(UserModel user, String provider, RealmModel realm) {
+ if (user.getAttribute("keycloak.socialLink." + provider + ".userId") != null) {
+ SocialLinkModel link = new SocialLinkModel();
+ link.setSocialProvider(provider);
+ link.setSocialUserId(user.getAttribute("keycloak.socialLink." + provider + ".userId"));
+ link.setSocialUsername(user.getAttribute("keycloak.socialLink." + provider + ".username"));
+ return link;
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public RoleModel getRoleById(String id, RealmModel realm) {
+ return mappings.wrap(realms().getRoleById(id, realm.getId()));
+ }
+
+ @Override
+ public ApplicationModel getApplicationById(String id, RealmModel realm) {
+ return mappings.wrap(realms().getApplicationById(id, realm.getId()));
+ }
+
+ @Override
+ public OAuthClientModel getOAuthClientById(String id, RealmModel realm) {
+ return mappings.wrap(realms().getOAuthClientById(id, realm.getId()));
+ }
+
+ @Override
+ public List<RealmModel> getRealms() {
+ return mappings.wrap(realms().getRealms());
+ }
+
+ @Override
+ public boolean removeRealm(String id) {
+ if (realms().removeRealm(id)) {
+ users().onRealmRemoved(id);
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public UsernameLoginFailureModel getUserLoginFailure(String username, RealmModel realm) {
+ return mappings.wrap(sessions().getUserLoginFailure(username, realm.getId()));
+ }
+
+ @Override
+ public UsernameLoginFailureModel addUserLoginFailure(String username, RealmModel realm) {
+ return mappings.wrap(sessions().addUserLoginFailure(username, realm.getId()));
+ }
+
+ @Override
+ public List<UsernameLoginFailureModel> getAllUserLoginFailures(RealmModel realm) {
+ return mappings.wrapLoginFailures(sessions().getAllUserLoginFailures(realm.getId()));
+ }
+
+ @Override
+ public UserSessionModel createUserSession(RealmModel realm, UserModel user, String ipAddress) {
+ return mappings.wrap(realm, sessions().createUserSession(realm.getId(), KeycloakModelUtils.generateId(), user.getId(), ipAddress));
+ }
+
+ @Override
+ public UserSessionModel getUserSession(String id, RealmModel realm) {
+ return mappings.wrap(realm, sessions().getUserSession(id, realm.getId()));
+ }
+
+ @Override
+ public List<UserSessionModel> getUserSessions(UserModel user, RealmModel realm) {
+ return mappings.wrapSessions(realm, sessions().getUserSessionsByUser(user.getId(), realm.getId()));
+ }
+
+ @Override
+ public Set<UserSessionModel> getUserSessions(RealmModel realm, ClientModel client) {
+ return mappings.wrapSessions(realm, sessions().getUserSessionsByClient(realm.getId(), client.getClientId()));
+ }
+
+ @Override
+ public int getActiveUserSessions(RealmModel realm, ClientModel client) {
+ return sessions().getActiveUserSessions(realm.getId(), client.getClientId());
+ }
+
+ @Override
+ public void removeUserSession(UserSessionModel session) {
+ sessions().removeUserSession(mappings.unwrap(session));
+ }
+
+ @Override
+ public void removeUserSessions(RealmModel realm, UserModel user) {
+ sessions().removeUserSessions(realm.getId(), user.getId());
+ }
+
+ @Override
+ public void removeExpiredUserSessions(RealmModel realm) {
+ long refreshTimeout = Time.currentTime() - realm.getSsoSessionIdleTimeout();
+ long sessionTimeout = Time.currentTime() - realm.getSsoSessionMaxLifespan();
+ sessions().removeExpiredUserSessions(realm.getId(), refreshTimeout, sessionTimeout);
+ }
+
+ @Override
+ public void removeUserSessions(RealmModel realm) {
+ sessions().removeUserSessions(realm.getId());
+ }
+
+ @Override
+ public void removeAllData() {
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridModelProviderFactory.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridModelProviderFactory.java
new file mode 100644
index 0000000..314ea96
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/HybridModelProviderFactory.java
@@ -0,0 +1,34 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.Config;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelProvider;
+import org.keycloak.models.ModelProviderFactory;
+import org.keycloak.models.sessions.SessionProvider;
+import org.keycloak.models.users.UserProvider;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class HybridModelProviderFactory implements ModelProviderFactory {
+
+ @Override
+ public String getId() {
+ return "hybrid";
+ }
+
+ @Override
+ public ModelProvider create(KeycloakSession session) {
+ return new HybridModelProvider(session);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void close() {
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/Mappings.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/Mappings.java
new file mode 100644
index 0000000..39972ae
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/Mappings.java
@@ -0,0 +1,220 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.realms.Client;
+import org.keycloak.models.realms.OAuthClient;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.sessions.LoginFailure;
+import org.keycloak.models.sessions.Session;
+import org.keycloak.models.users.User;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class Mappings {
+
+ private final HybridModelProvider provider;
+ private Map<Object, Object> mappings = new HashMap<Object, Object>();
+
+ public Mappings(HybridModelProvider provider) {
+ this.provider = provider;
+ }
+
+ public RealmModel wrap(Realm realm) {
+ if (realm == null) return null;
+
+ RealmAdapter adapter = (RealmAdapter) mappings.get(realm);
+ if (adapter == null) {
+ adapter = new RealmAdapter(provider, realm);
+ mappings.put(realm, adapter);
+ }
+ return adapter;
+ }
+
+ public List<RealmModel> wrap(List<Realm> realms) {
+ List<RealmModel> adapters = new LinkedList<RealmModel>();
+ for (Realm realm : realms) {
+ adapters.add(wrap(realm));
+ }
+ return adapters;
+ }
+
+ public RoleModel wrap(Role role) {
+ if (role == null) return null;
+
+ RoleAdapter adapter = (RoleAdapter) mappings.get(role);
+ if (adapter == null) {
+ adapter = new RoleAdapter(provider, role);
+ mappings.put(role, adapter);
+ }
+ return adapter;
+ }
+
+ public Set<RoleModel> wrap(Set<Role> roles) {
+ Set<RoleModel> adapters = new HashSet<RoleModel>();
+ for (Role role : roles) {
+ adapters.add(wrap(role));
+ }
+ return adapters;
+ }
+
+ public Role unwrap(RoleModel role) {
+ if (role instanceof RoleAdapter) {
+ return ((RoleAdapter) role).getRole();
+ } else {
+ return provider.realms().getRoleById(role.getId(), getRealm(role.getContainer()));
+ }
+ }
+
+ public ApplicationModel wrap(Application application) {
+ return application != null ? new ApplicationAdapter(provider, application) : null;
+ }
+
+ public List<ApplicationModel> wrapApps(List<Application> applications) {
+ List<ApplicationModel> adapters = new LinkedList<ApplicationModel>();
+ for (Application application : applications) {
+ adapters.add(wrap(application));
+ }
+ return adapters;
+ }
+
+ public Map<String, ApplicationModel> wrap(Map<String, Application> applications) {
+ Map<String, ApplicationModel> adapters = new HashMap<String, ApplicationModel>();
+ for (Map.Entry<String, Application> e : applications.entrySet()) {
+ adapters.put(e.getKey(), wrap(e.getValue()));
+ }
+ return adapters;
+ }
+
+ public OAuthClientModel wrap(OAuthClient client) {
+ return client != null ? new OAuthClientAdapter(provider, client) : null;
+ }
+
+ public List<OAuthClientModel> wrapClients(List<OAuthClient> clients) {
+ List<OAuthClientModel> adapters = new LinkedList<OAuthClientModel>();
+ for (OAuthClient client : clients) {
+ adapters.add(wrap(client));
+ }
+ return adapters;
+ }
+
+ public Client unwrap(ClientModel client) {
+ if (client == null) {
+ return null;
+ }
+
+ if (client instanceof ApplicationAdapter) {
+ return ((ApplicationAdapter) client).getApplication();
+ } else if (client instanceof OAuthClientAdapter) {
+ return ((OAuthClientAdapter) client).getOauthClient();
+ } else {
+ throw new IllegalArgumentException("Not a hybrid model");
+ }
+ }
+
+ public Application unwrap(ApplicationModel application) {
+ if (application == null) {
+ return null;
+ }
+
+ if (!(application instanceof ApplicationAdapter)) {
+ throw new IllegalArgumentException("Not a hybrid model");
+ }
+
+ return ((ApplicationAdapter) application).getApplication();
+ }
+
+ public UserModel wrap(RealmModel realm, User user) {
+ return user != null ? new UserAdapter(provider, realm, user) : null;
+ }
+
+ public List<UserModel> wrapUsers(RealmModel realm, List<User> users) {
+ List<UserModel> adapters = new LinkedList<UserModel>();
+ for (User user : users) {
+ adapters.add(wrap(realm, user));
+ }
+ return adapters;
+ }
+
+ public static User unwrap(UserModel user) {
+ if (user == null) {
+ return null;
+ }
+
+ if (!(user instanceof UserAdapter)) {
+ throw new IllegalArgumentException("Not a hybrid model");
+ }
+
+ return ((UserAdapter) user).getUser();
+ }
+
+ public UserSessionModel wrap(RealmModel realm, Session session) {
+ return session != null ? new UserSessionAdapter(provider, realm, session) : null;
+ }
+
+ public List<UserSessionModel> wrapSessions(RealmModel realm, List<Session> sessions) {
+ List<UserSessionModel> adapters = new LinkedList<UserSessionModel>();
+ for (Session session : sessions) {
+ adapters.add(wrap(realm, session));
+ }
+ return adapters;
+ }
+
+ public Set<UserSessionModel> wrapSessions(RealmModel realm, Set<Session> sessions) {
+ Set<UserSessionModel> adapters = new HashSet<UserSessionModel>();
+ for (Session session : sessions) {
+ adapters.add(wrap(realm, session));
+ }
+ return adapters;
+ }
+
+ public Session unwrap(UserSessionModel session) {
+ if (session == null) {
+ return null;
+ }
+
+ if (!(session instanceof UserSessionAdapter)) {
+ throw new IllegalArgumentException("Not a hybrid model");
+ }
+
+ return ((UserSessionAdapter) session).getSession();
+ }
+
+ public UsernameLoginFailureModel wrap(LoginFailure loginFailure) {
+ return loginFailure != null ? new UsernameLoginFailureAdapter(provider, loginFailure) : null;
+ }
+
+ public List<UsernameLoginFailureModel> wrapLoginFailures(List<LoginFailure> loginFailures) {
+ List<UsernameLoginFailureModel> adapters = new LinkedList<UsernameLoginFailureModel>();
+ for (LoginFailure loginFailure : loginFailures) {
+ adapters.add(wrap(loginFailure));
+ }
+ return adapters;
+ }
+
+ private String getRealm(RoleContainerModel container) {
+ if (container instanceof RealmModel) {
+ return ((RealmModel) container).getId();
+ } else {
+ return ((ApplicationModel) container).getRealm().getId();
+ }
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/OAuthClientAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/OAuthClientAdapter.java
new file mode 100644
index 0000000..308e819
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/OAuthClientAdapter.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.realms.OAuthClient;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class OAuthClientAdapter extends ClientAdapter implements OAuthClientModel {
+
+ private OAuthClient oauthClient;
+
+ OAuthClientAdapter(HybridModelProvider provider, OAuthClient oauthClient) {
+ super(provider, oauthClient);
+ this.oauthClient = oauthClient;
+ }
+
+ OAuthClient getOauthClient() {
+ return oauthClient;
+ }
+
+ @Override
+ public void setClientId(String id) {
+ oauthClient.setClientId(id);
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/RealmAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/RealmAdapter.java
new file mode 100644
index 0000000..2f3e0a2
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/RealmAdapter.java
@@ -0,0 +1,830 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.realms.Client;
+import org.keycloak.models.realms.OAuthClient;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.OAuthClientModel;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.users.Credentials;
+import org.keycloak.models.users.Feature;
+import org.keycloak.models.users.User;
+import org.keycloak.models.utils.KeycloakModelUtils;
+import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+import org.keycloak.models.utils.TimeBasedOTP;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RealmAdapter implements RealmModel {
+
+ private HybridModelProvider provider;
+ private Realm realm;
+
+ RealmAdapter(HybridModelProvider provider, Realm realm) {
+ this.provider = provider;
+ this.realm = realm;
+ }
+
+ Realm getRealm() {
+ return realm;
+ }
+
+ @Override
+ public String getId() {
+ return realm.getId();
+ }
+
+ @Override
+ public String getName() {
+ return realm.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ realm.setName(name);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return realm.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ realm.setEnabled(enabled);
+ }
+
+ @Override
+ public boolean isSslNotRequired() {
+ return realm.isSslNotRequired();
+ }
+
+ @Override
+ public void setSslNotRequired(boolean sslNotRequired) {
+ realm.setSslNotRequired(sslNotRequired);
+ }
+
+ @Override
+ public boolean isRegistrationAllowed() {
+ return realm.isRegistrationAllowed();
+ }
+
+ @Override
+ public void setRegistrationAllowed(boolean registrationAllowed) {
+ realm.setRegistrationAllowed(registrationAllowed);
+ }
+
+ @Override
+ public boolean isPasswordCredentialGrantAllowed() {
+ return realm.isPasswordCredentialGrantAllowed();
+ }
+
+ @Override
+ public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
+ realm.setPasswordCredentialGrantAllowed(passwordCredentialGrantAllowed);
+ }
+
+ @Override
+ public boolean isRememberMe() {
+ return realm.isRememberMe();
+ }
+
+ @Override
+ public void setRememberMe(boolean rememberMe) {
+ realm.setRememberMe(rememberMe);
+ }
+
+ @Override
+ public boolean isBruteForceProtected() {
+ return realm.isBruteForceProtected();
+ }
+
+ @Override
+ public void setBruteForceProtected(boolean value) {
+ realm.setBruteForceProtected(value);
+ }
+
+ @Override
+ public int getMaxFailureWaitSeconds() {
+ return realm.getMaxFailureWaitSeconds();
+ }
+
+ @Override
+ public void setMaxFailureWaitSeconds(int val) {
+ realm.setMaxFailureWaitSeconds(val);
+ }
+
+ @Override
+ public int getWaitIncrementSeconds() {
+ return realm.getWaitIncrementSeconds();
+ }
+
+ @Override
+ public void setWaitIncrementSeconds(int val) {
+ realm.setWaitIncrementSeconds(val);
+ }
+
+ @Override
+ public int getMinimumQuickLoginWaitSeconds() {
+ return realm.getMinimumQuickLoginWaitSeconds();
+ }
+
+ @Override
+ public void setMinimumQuickLoginWaitSeconds(int val) {
+ realm.setMinimumQuickLoginWaitSeconds(val);
+ }
+
+ @Override
+ public long getQuickLoginCheckMilliSeconds() {
+ return realm.getQuickLoginCheckMilliSeconds();
+ }
+
+ @Override
+ public void setQuickLoginCheckMilliSeconds(long val) {
+ realm.setQuickLoginCheckMilliSeconds(val);
+ }
+
+ @Override
+ public int getMaxDeltaTimeSeconds() {
+ return realm.getMaxDeltaTimeSeconds();
+ }
+
+ @Override
+ public void setMaxDeltaTimeSeconds(int val) {
+ realm.setMaxDeltaTimeSeconds(val);
+ }
+
+ @Override
+ public int getFailureFactor() {
+ return realm.getFailureFactor();
+ }
+
+ @Override
+ public void setFailureFactor(int failureFactor) {
+ realm.setFailureFactor(failureFactor);
+ }
+
+ @Override
+ public boolean isVerifyEmail() {
+ return realm.isVerifyEmail();
+ }
+
+ @Override
+ public void setVerifyEmail(boolean verifyEmail) {
+ realm.setVerifyEmail(verifyEmail);
+ }
+
+ @Override
+ public boolean isResetPasswordAllowed() {
+ return realm.isResetPasswordAllowed();
+ }
+
+ @Override
+ public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
+ realm.setResetPasswordAllowed(resetPasswordAllowed);
+ }
+
+ @Override
+ public int getSsoSessionIdleTimeout() {
+ return realm.getSsoSessionIdleTimeout();
+ }
+
+ @Override
+ public void setSsoSessionIdleTimeout(int seconds) {
+ realm.setSsoSessionIdleTimeout(seconds);
+ }
+
+ @Override
+ public int getSsoSessionMaxLifespan() {
+ return realm.getSsoSessionMaxLifespan();
+ }
+
+ @Override
+ public void setSsoSessionMaxLifespan(int seconds) {
+ realm.setSsoSessionMaxLifespan(seconds);
+ }
+
+ @Override
+ public int getAccessTokenLifespan() {
+ return realm.getAccessTokenLifespan();
+ }
+
+ @Override
+ public void setAccessTokenLifespan(int seconds) {
+ realm.setAccessTokenLifespan(seconds);
+ }
+
+ @Override
+ public int getAccessCodeLifespan() {
+ return realm.getAccessCodeLifespan();
+ }
+
+ @Override
+ public void setAccessCodeLifespan(int seconds) {
+ realm.setAccessCodeLifespan(seconds);
+ }
+
+ @Override
+ public int getAccessCodeLifespanUserAction() {
+ return realm.getAccessCodeLifespanUserAction();
+ }
+
+ @Override
+ public void setAccessCodeLifespanUserAction(int seconds) {
+ realm.setAccessCodeLifespanUserAction(seconds);
+ }
+
+ @Override
+ public String getPublicKeyPem() {
+ return realm.getPublicKeyPem();
+ }
+
+ @Override
+ public void setPublicKeyPem(String publicKeyPem) {
+ realm.setPublicKeyPem(publicKeyPem);
+ }
+
+ @Override
+ public String getPrivateKeyPem() {
+ return realm.getPrivateKeyPem();
+ }
+
+ @Override
+ public void setPrivateKeyPem(String privateKeyPem) {
+ realm.setPrivateKeyPem(privateKeyPem);
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ return realm.getPublicKey();
+ }
+
+ @Override
+ public void setPublicKey(PublicKey publicKey) {
+ realm.setPublicKey(publicKey);
+ }
+
+ @Override
+ public PrivateKey getPrivateKey() {
+ return realm.getPrivateKey();
+ }
+
+ @Override
+ public void setPrivateKey(PrivateKey privateKey) {
+ realm.setPrivateKey(privateKey);
+ }
+
+ @Override
+ public List<RequiredCredentialModel> getRequiredCredentials() {
+ return realm.getRequiredCredentials();
+ }
+
+ @Override
+ public void addRequiredCredential(String cred) {
+ realm.addRequiredCredential(cred);
+ }
+
+ @Override
+ public PasswordPolicy getPasswordPolicy() {
+ return realm.getPasswordPolicy();
+ }
+
+ @Override
+ public void setPasswordPolicy(PasswordPolicy policy) {
+ realm.setPasswordPolicy(policy);
+ }
+
+ @Override
+ public boolean validatePassword(UserModel userModel, String password) {
+ if (provider.users().supports(Feature.VERIFY_CREDENTIALS)) {
+ User user = provider.mappings().unwrap(userModel);
+ return provider.users().verifyCredentials(user, new Credentials(UserCredentialModel.PASSWORD, password));
+ } else {
+ for (UserCredentialValueModel cred : userModel.getCredentialsDirectly()) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ return new Pbkdf2PasswordEncoder(cred.getSalt()).verify(password, cred.getValue());
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public boolean validateTOTP(UserModel userModel, String password, String token) {
+ if (provider.users().supports(Feature.VERIFY_CREDENTIALS)) {
+ User user = provider.mappings().unwrap(userModel);
+ return provider.users().verifyCredentials(user, new Credentials(UserCredentialModel.PASSWORD, password),
+ new Credentials(UserCredentialModel.TOTP, token));
+ } else {
+ if (!validatePassword(userModel, password)) return false;
+ for (UserCredentialValueModel cred : userModel.getCredentialsDirectly()) {
+ if (cred.getType().equals(UserCredentialModel.TOTP)) {
+ return new TimeBasedOTP().validate(token, cred.getValue().getBytes());
+ }
+ }
+ return false;
+ }
+ }
+
+ @Override
+ public UserModel getUser(String name) {
+ return provider.getUserByUsername(name, this);
+ }
+
+ @Override
+ public UserModel getUserByEmail(String email) {
+ return provider.getUserByEmail(email, this);
+ }
+
+ @Override
+ public UserModel getUserById(String name) {
+ return provider.getUserById(name, this);
+ }
+
+ @Override
+ public UserModel addUser(String id, String username, boolean addDefaultRoles) {
+ if (id == null) {
+ id = KeycloakModelUtils.generateId();
+ }
+
+ Set<String> initialRoles = new HashSet<String>();
+
+ if (addDefaultRoles) {
+ for (String r : realm.getDefaultRoles()) {
+ initialRoles.add(realm.getRole(r).getId());
+ }
+
+ for (Application app : realm.getApplications()) {
+ for (String r : app.getDefaultRoles()) {
+ initialRoles.add(app.getRole(r).getId());
+ }
+ }
+ }
+
+ return provider.mappings().wrap(this, provider.users().addUser(id, username, initialRoles, realm.getId()));
+ }
+
+ @Override
+ public UserModel addUser(String username) {
+ return addUser(null, username, true);
+ }
+
+ @Override
+ public boolean removeUser(String name) {
+ return provider.users().removeUser(name, realm.getId());
+ }
+
+ @Override
+ public RoleModel getRoleById(String id) {
+ return provider.mappings().wrap(provider.realms().getRoleById(id, realm.getId()));
+ }
+
+ @Override
+ public List<String> getDefaultRoles() {
+ return realm.getDefaultRoles();
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ if (getRole(name) == null) {
+ addRole(name);
+ }
+
+ realm.addDefaultRole(name);
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ for (String name : defaultRoles) {
+ if (getRole(name) == null) {
+ addRole(name);
+ }
+ }
+
+ realm.updateDefaultRoles(defaultRoles);
+ }
+
+ @Override
+ public ClientModel findClient(String clientId) {
+ Client client = realm.findClient(clientId);
+ if (client instanceof Application) {
+ return provider.mappings().wrap((Application) client);
+ } else if (client instanceof OAuthClient) {
+ return provider.mappings().wrap((OAuthClient) client);
+ } else {
+ throw new IllegalArgumentException("Unsupported client type");
+ }
+ }
+
+ @Override
+ public Map<String, ApplicationModel> getApplicationNameMap() {
+ return provider.mappings().wrap(realm.getApplicationNameMap());
+ }
+
+ @Override
+ public List<ApplicationModel> getApplications() {
+ return provider.mappings().wrapApps(realm.getApplications());
+ }
+
+ @Override
+ public ApplicationModel addApplication(String name) {
+ return addApplication(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public ApplicationModel addApplication(String id, String name) {
+ return provider.mappings().wrap(realm.addApplication(id, name));
+ }
+
+ @Override
+ public boolean removeApplication(String id) {
+ Application application = provider.realms().getApplicationById(id, realm.getId());
+ if (application != null) {
+ return realm.removeApplication(application);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public ApplicationModel getApplicationById(String id) {
+ return provider.getApplicationById(id, this);
+ }
+
+ @Override
+ public ApplicationModel getApplicationByName(String name) {
+ return provider.mappings().wrap(realm.getApplicationByName(name));
+ }
+
+ @Override
+ public void updateRequiredCredentials(Set<String> creds) {
+ realm.updateRequiredCredentials(creds);
+ }
+
+ @Override
+ public UserModel getUserBySocialLink(SocialLinkModel socialLink) {
+ return provider.getUserBySocialLink(socialLink, this);
+ }
+
+ @Override
+ public Set<SocialLinkModel> getSocialLinks(UserModel user) {
+ return provider.getSocialLinks(user, this);
+ }
+
+ @Override
+ public SocialLinkModel getSocialLink(UserModel user, String socialProvider) {
+ return provider.getSocialLink(user, socialProvider, this);
+ }
+
+ @Override
+ public void addSocialLink(UserModel user, SocialLinkModel socialLink) {
+ user.setAttribute("keycloak.socialLink." + socialLink.getSocialProvider() + ".userId", socialLink.getSocialUserId());
+ user.setAttribute("keycloak.socialLink." + socialLink.getSocialProvider() + ".username", socialLink.getSocialUsername());
+ }
+
+ @Override
+ public boolean removeSocialLink(UserModel user, String socialProvider) {
+ if (user.getAttribute("keycloak.socialLink." + socialProvider + ".userId") != null) {
+ user.removeAttribute("keycloak.socialLink." + socialProvider + ".userId");
+ user.removeAttribute("keycloak.socialLink." + socialProvider + ".username");
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public boolean isSocial() {
+ return realm.isSocial();
+ }
+
+ @Override
+ public void setSocial(boolean social) {
+ realm.setSocial(social);
+ }
+
+ @Override
+ public boolean isUpdateProfileOnInitialSocialLogin() {
+ return realm.isUpdateProfileOnInitialSocialLogin();
+ }
+
+ @Override
+ public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
+ realm.setUpdateProfileOnInitialSocialLogin(updateProfileOnInitialSocialLogin);
+ }
+
+ @Override
+ public UsernameLoginFailureModel getUserLoginFailure(String username) {
+ return provider.getUserLoginFailure(username, this);
+ }
+
+ @Override
+ public UsernameLoginFailureModel addUserLoginFailure(String username) {
+ return provider.addUserLoginFailure(username, this);
+ }
+
+ @Override
+ public List<UsernameLoginFailureModel> getAllUserLoginFailures() {
+ return provider.getAllUserLoginFailures(this);
+ }
+
+ @Override
+ public List<UserModel> getUsers() {
+ return provider.getUsers(this);
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search) {
+ return provider.searchForUser(search, this);
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes) {
+ return provider.searchForUserByAttributes(attributes, this);
+ }
+
+ @Override
+ public OAuthClientModel addOAuthClient(String name) {
+ return addOAuthClient(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public OAuthClientModel addOAuthClient(String id, String name) {
+ return provider.mappings().wrap(realm.addOAuthClient(id, name));
+ }
+
+ @Override
+ public OAuthClientModel getOAuthClient(String name) {
+ return provider.mappings().wrap(realm.getOAuthClient(name));
+ }
+
+ @Override
+ public OAuthClientModel getOAuthClientById(String id) {
+ return provider.getOAuthClientById(id, this);
+ }
+
+ @Override
+ public boolean removeOAuthClient(String id) {
+ OAuthClient client = provider.realms().getOAuthClientById(id, realm.getId());
+ if (client != null) {
+ return realm.removeOAuthClient(client);
+ } else {
+ return false;
+ }
+ }
+
+ @Override
+ public List<OAuthClientModel> getOAuthClients() {
+ return provider.mappings().wrapClients(realm.getOAuthClients());
+ }
+
+ @Override
+ public Map<String, String> getSmtpConfig() {
+ return realm.getSmtpConfig();
+ }
+
+ @Override
+ public void setSmtpConfig(Map<String, String> smtpConfig) {
+ realm.setSmtpConfig(smtpConfig);
+ }
+
+ @Override
+ public Map<String, String> getSocialConfig() {
+ return realm.getSocialConfig();
+ }
+
+ @Override
+ public void setSocialConfig(Map<String, String> socialConfig) {
+ realm.setSocialConfig(socialConfig);
+ }
+
+ @Override
+ public Map<String, String> getLdapServerConfig() {
+ return realm.getLdapServerConfig();
+ }
+
+ @Override
+ public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+ realm.setLdapServerConfig(ldapServerConfig);
+ }
+
+ @Override
+ public List<AuthenticationProviderModel> getAuthenticationProviders() {
+ return realm.getAuthenticationProviders();
+ }
+
+ @Override
+ public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
+ realm.setAuthenticationProviders(authenticationProviders);
+ }
+
+ @Override
+ public String getLoginTheme() {
+ return realm.getLoginTheme();
+ }
+
+ @Override
+ public void setLoginTheme(String name) {
+ realm.setLoginTheme(name);
+ }
+
+ @Override
+ public String getAccountTheme() {
+ return realm.getAccountTheme();
+ }
+
+ @Override
+ public void setAccountTheme(String name) {
+ realm.setAccountTheme(name);
+ }
+
+ @Override
+ public String getAdminTheme() {
+ return realm.getAdminTheme();
+ }
+
+ @Override
+ public void setAdminTheme(String name) {
+ realm.setAdminTheme(name);
+ }
+
+ @Override
+ public String getEmailTheme() {
+ return realm.getEmailTheme();
+ }
+
+ @Override
+ public void setEmailTheme(String name) {
+ realm.setEmailTheme(name);
+ }
+
+ @Override
+ public int getNotBefore() {
+ return realm.getNotBefore();
+ }
+
+ @Override
+ public void setNotBefore(int notBefore) {
+ realm.setNotBefore(notBefore);
+ }
+
+ @Override
+ public boolean isAuditEnabled() {
+ return realm.isAuditEnabled();
+ }
+
+ @Override
+ public void setAuditEnabled(boolean enabled) {
+ realm.setAuditEnabled(enabled);
+ }
+
+ @Override
+ public long getAuditExpiration() {
+ return realm.getAuditExpiration();
+ }
+
+ @Override
+ public void setAuditExpiration(long expiration) {
+ realm.setAuditExpiration(expiration);
+ }
+
+ @Override
+ public Set<String> getAuditListeners() {
+ return realm.getAuditListeners();
+ }
+
+ @Override
+ public void setAuditListeners(Set<String> listeners) {
+ realm.setAuditListeners(listeners);
+ }
+
+ @Override
+ public ApplicationModel getMasterAdminApp() {
+ return provider.mappings().wrap(realm.getMasterAdminApp());
+ }
+
+ @Override
+ public void setMasterAdminApp(ApplicationModel app) {
+ realm.setMasterAdminApp(provider.mappings().unwrap(app));
+ }
+
+ @Override
+ public UserSessionModel createUserSession(UserModel user, String ipAddress) {
+ return provider.createUserSession(this, user, ipAddress);
+ }
+
+ @Override
+ public UserSessionModel getUserSession(String id) {
+ return provider.getUserSession(id, this);
+ }
+
+ @Override
+ public List<UserSessionModel> getUserSessions(UserModel user) {
+ return provider.getUserSessions(user, this);
+ }
+
+ @Override
+ public void removeUserSession(UserSessionModel session) {
+ provider.removeUserSession(session);
+ }
+
+ @Override
+ public void removeUserSessions(UserModel user) {
+ provider.removeUserSessions(this, user);
+ }
+
+ @Override
+ public void removeExpiredUserSessions() {
+ provider.removeExpiredUserSessions(this);
+ }
+
+ @Override
+ public ClientModel findClientById(String id) {
+ Application application = provider.realms().getApplicationById(id, realm.getId());
+ if (application != null) {
+ return provider.mappings().wrap(application);
+ }
+
+ OAuthClient client = provider.realms().getOAuthClientById(id, realm.getId());
+ if (client != null) {
+ return provider.mappings().wrap(client);
+ }
+
+ return null;
+ }
+
+ @Override
+ public void removeUserSessions() {
+ provider.removeUserSessions(this);
+ }
+
+ @Override
+ public RoleModel getRole(String name) {
+ return provider.mappings().wrap(realm.getRole(name));
+ }
+
+ @Override
+ public RoleModel addRole(String name) {
+ return addRole(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public RoleModel addRole(String id, String name) {
+ return provider.mappings().wrap(realm.addRole(id, name));
+
+ }
+
+ @Override
+ public boolean removeRoleById(String id) {
+ RoleModel role = getRoleById(id);
+ if (role != null) {
+ if (role.getContainer().removeRole(role)) {
+ provider.users().onRoleRemoved(role.getId());
+ return true;
+ } else {
+ return false;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean removeRole(RoleModel role) {
+ return removeRoleById(role.getId());
+ }
+
+ @Override
+ public Set<RoleModel> getRoles() {
+ return provider.mappings().wrap(realm.getRoles());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof RealmModel)) return false;
+
+ RealmModel that = (RealmModel) o;
+ return that.getId().equals(getId());
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/RoleAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/RoleAdapter.java
new file mode 100644
index 0000000..c6e79ef
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/RoleAdapter.java
@@ -0,0 +1,114 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.RoleContainerModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RoleAdapter implements RoleModel {
+
+ private HybridModelProvider provider;
+
+ private Role role;
+
+ RoleAdapter(HybridModelProvider provider, Role role) {
+ this.provider = provider;
+ this.role = role;
+ }
+
+ Role getRole() {
+ return role;
+ }
+
+ @Override
+ public String getName() {
+ return role.getName();
+ }
+
+ @Override
+ public String getDescription() {
+ return role.getDescription();
+ }
+
+ @Override
+ public void setDescription(String description) {
+ role.setDescription(description);
+ }
+
+ @Override
+ public String getId() {
+ return role.getId();
+ }
+
+ @Override
+ public void setName(String name) {
+ role.setName(name);
+ }
+
+ @Override
+ public boolean isComposite() {
+ return role.isComposite();
+ }
+
+ @Override
+ public void addCompositeRole(RoleModel role) {
+ this.role.addCompositeRole(provider.mappings().unwrap(role));
+ }
+
+ @Override
+ public void removeCompositeRole(RoleModel role) {
+ this.role.removeCompositeRole(provider.mappings().unwrap(role));
+ }
+
+ @Override
+ public Set<RoleModel> getComposites() {
+ return provider.mappings().wrap(role.getComposites());
+ }
+
+ @Override
+ public RoleContainerModel getContainer() {
+ if (role.getContainer() instanceof Application) {
+ return provider.mappings().wrap((Application) role.getContainer());
+ } else if (role.getContainer() instanceof Realm) {
+ return provider.mappings().wrap((Realm) role.getContainer());
+ } else {
+ throw new IllegalArgumentException("Unsupported role container");
+ }
+ }
+
+ @Override
+ public boolean hasRole(RoleModel role) {
+ if (this.equals(role)) {
+ return true;
+ }
+ if (!isComposite()) {
+ return false;
+ }
+
+ Set<RoleModel> visited = new HashSet<RoleModel>();
+ return KeycloakModelUtils.searchFor(role, this, visited);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof RoleModel)) return false;
+
+ RoleModel that = (RoleModel) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserAdapter.java
new file mode 100644
index 0000000..e082fc6
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserAdapter.java
@@ -0,0 +1,325 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.users.Attributes;
+import org.keycloak.models.users.Credentials;
+import org.keycloak.models.users.Feature;
+import org.keycloak.models.users.User;
+import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.keycloak.models.utils.Pbkdf2PasswordEncoder.getSalt;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserAdapter implements UserModel {
+
+ private HybridModelProvider provider;
+ private RealmModel realm;
+ private User user;
+
+ UserAdapter(HybridModelProvider provider, RealmModel realm, User user) {
+ this.provider = provider;
+ this.realm = realm;
+ this.user = user;
+ }
+
+ User getUser() {
+ return user;
+ }
+
+ @Override
+ public String getId() {
+ return user.getId();
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+ @Override
+ public void setUsername(String username) {
+ user.setUsername(username);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return user.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ user.setEnabled(enabled);
+ }
+
+ @Override
+ public void setAttribute(String name, String value) {
+ user.setAttribute(name, value);
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ user.removeAttribute(name);
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ return user.getAttribute(name);
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+ return user.getAttributes();
+ }
+
+ @Override
+ public Set<RequiredAction> getRequiredActions() {
+ String value = user.getAttribute(Attributes.REQUIRED_ACTIONS);
+ if (value == null) {
+ return Collections.emptySet();
+ }
+
+ Set<RequiredAction> actions = new HashSet<RequiredAction>();
+ for (String a : value.substring(1, value.length() - 1).split(",")) {
+ actions.add(RequiredAction.valueOf(a.trim()));
+ }
+ return actions;
+ }
+
+ @Override
+ public void addRequiredAction(RequiredAction action) {
+ Set<RequiredAction> actions;
+ if (user.getAttribute(Attributes.REQUIRED_ACTIONS) == null) {
+ actions = new HashSet<RequiredAction>();
+ } else {
+ actions = getRequiredActions();
+ }
+
+ if (!actions.contains(action)) {
+ actions.add(action);
+ user.setAttribute(Attributes.REQUIRED_ACTIONS, actions.toString());
+ }
+ }
+
+ @Override
+ public void removeRequiredAction(RequiredAction action) {
+ Set<RequiredAction> actions = getRequiredActions();
+ if (actions.contains(action)) {
+ actions.remove(action);
+
+ if (actions.isEmpty()) {
+ user.removeAttribute(Attributes.REQUIRED_ACTIONS);
+ } else {
+ user.setAttribute(Attributes.REQUIRED_ACTIONS, actions.toString());
+ }
+ }
+ }
+
+ @Override
+ public String getFirstName() {
+ return user.getFirstName();
+ }
+
+ @Override
+ public void setFirstName(String firstName) {
+ user.setFirstName(firstName);
+ }
+
+ @Override
+ public String getLastName() {
+ return user.getLastName();
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ user.setLastName(lastName);
+ }
+
+ @Override
+ public String getEmail() {
+ return user.getEmail();
+ }
+
+ @Override
+ public void setEmail(String email) {
+ user.setEmail(email);
+ }
+
+ @Override
+ public boolean isEmailVerified() {
+ return isBooleanAttribute(Attributes.EMAIL_VERIFIED);
+ }
+
+ @Override
+ public void setEmailVerified(boolean verified) {
+ setBooleanAttribute(Attributes.EMAIL_VERIFIED, verified);
+ }
+
+ @Override
+ public boolean isTotp() {
+ return isBooleanAttribute(Attributes.TOTP_ENABLED);
+ }
+
+ @Override
+ public void setTotp(boolean totp) {
+ setBooleanAttribute(Attributes.TOTP_ENABLED, totp);
+ }
+
+ @Override
+ public void updateCredential(UserCredentialModel model) {
+ if (provider.users().supports(Feature.UPDATE_CREDENTIALS)) {
+ Credentials credentials;
+
+ if (model.getType().equals(UserCredentialModel.PASSWORD)) {
+ byte[] salt = getSalt();
+ int hashIterations = 1;
+ PasswordPolicy policy = realm.getPasswordPolicy();
+ if (policy != null) {
+ hashIterations = policy.getHashIterations();
+ if (hashIterations == -1) hashIterations = 1;
+ }
+ String value = new Pbkdf2PasswordEncoder(salt).encode(model.getValue(), hashIterations);
+
+ credentials = new Credentials(model.getType(), salt, value, hashIterations, model.getDevice());
+ } else {
+ credentials = new Credentials(model.getType(), model.getValue(), model.getDevice());
+ }
+
+ user.updateCredential(credentials);
+ } else {
+ throw new RuntimeException("Users store doesn't support updating credentials");
+ }
+ }
+
+ @Override
+ public List<UserCredentialValueModel> getCredentialsDirectly() {
+ if (provider.users().supports(Feature.READ_CREDENTIALS)) {
+ List<UserCredentialValueModel> models = new LinkedList<UserCredentialValueModel>();
+ for (Credentials cred : user.getCredentials()) {
+ UserCredentialValueModel model = new UserCredentialValueModel();
+ model.setType(cred.getType());
+ model.setValue(cred.getValue());
+ model.setDevice(cred.getDevice());
+ model.setSalt(cred.getSalt());
+ model.setHashIterations(cred.getHashIterations());
+ models.add(model);
+ }
+ return models;
+ } else {
+ throw new IllegalStateException("Users provider doesn't support reading credentials");
+ }
+ }
+
+ @Override
+ public void updateCredentialDirectly(UserCredentialValueModel model) {
+ if (provider.users().supports(Feature.UPDATE_CREDENTIALS)) {
+ Credentials credentials = new Credentials(model.getType(), model.getSalt(), model.getValue(), model.getHashIterations(), model.getDevice());
+ user.updateCredential(credentials);
+ } else {
+ throw new IllegalStateException("Users provider doesn't support updating credentials");
+ }
+ }
+
+ @Override
+ public AuthenticationLinkModel getAuthenticationLink() {
+ for (Map.Entry<String, String> e : user.getAttributes().entrySet()) {
+ if (e.getKey().matches("keycloak\\.authenticationLink\\..*\\.userId")) {
+ String provider = e.getKey().split("\\.")[2];
+ return new AuthenticationLinkModel(provider, e.getValue());
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void setAuthenticationLink(AuthenticationLinkModel authenticationLink) {
+ Iterator<Map.Entry<String, String>> itr = user.getAttributes().entrySet().iterator();
+ while (itr.hasNext()) {
+ if (itr.next().getKey().matches("keycloak\\.authenticationLink\\..*\\.userId")) {
+ itr.remove();
+ }
+ }
+ user.setAttribute("keycloak.authenticationLink." + authenticationLink.getAuthProvider() + ".userId", authenticationLink.getAuthUserId());
+ }
+
+ @Override
+ public Set<RoleModel> getRealmRoleMappings() {
+ Set<RoleModel> roles = new HashSet<RoleModel>();
+ for (RoleModel r : getRoleMappings()) {
+ if (r.getContainer() instanceof RealmModel) {
+ roles.add(r);
+ }
+ }
+ return roles;
+ }
+
+ @Override
+ public Set<RoleModel> getApplicationRoleMappings(ApplicationModel app) {
+ Set<RoleModel> roles = new HashSet<RoleModel>();
+ for (RoleModel r : getRoleMappings()) {
+ if (r.getContainer() instanceof ApplicationModel && ((ApplicationModel) r.getContainer()).getId().equals(app.getId())) {
+ roles.add(r);
+ }
+ }
+ return roles;
+ }
+
+ @Override
+ public boolean hasRole(RoleModel role) {
+ for (RoleModel r : getRoleMappings()) {
+ if (r.hasRole(role)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void grantRole(RoleModel role) {
+ user.grantRole(role.getId());
+ }
+
+ @Override
+ public Set<RoleModel> getRoleMappings() {
+ Set<RoleModel> roles = new HashSet<RoleModel>();
+ for (String r : user.getRoleMappings()) {
+ roles.add(realm.getRoleById(r));
+ }
+ return roles;
+ }
+
+ @Override
+ public void deleteRoleMapping(RoleModel role) {
+ user.deleteRoleMapping(role.getId());
+ }
+
+ private boolean isBooleanAttribute(String name) {
+ String v = user.getAttribute(name);
+ return v != null ? v.equals("true") : false;
+ }
+
+ private void setBooleanAttribute(String name, boolean enable) {
+ if (enable) {
+ user.setAttribute(name, "true");
+ } else {
+ user.removeAttribute(name);
+ }
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/UsernameLoginFailureAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UsernameLoginFailureAdapter.java
new file mode 100644
index 0000000..35b9539
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UsernameLoginFailureAdapter.java
@@ -0,0 +1,73 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.UsernameLoginFailureModel;
+import org.keycloak.models.sessions.LoginFailure;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UsernameLoginFailureAdapter implements UsernameLoginFailureModel {
+
+ private HybridModelProvider provider;
+
+ private LoginFailure loginFailure;
+
+ UsernameLoginFailureAdapter(HybridModelProvider provider, LoginFailure loginFailure) {
+ this.provider = provider;
+ this.loginFailure = loginFailure;
+ }
+
+ @Override
+ public String getUsername() {
+ return loginFailure.getUsername();
+ }
+
+ @Override
+ public int getFailedLoginNotBefore() {
+ return loginFailure.getFailedLoginNotBefore();
+ }
+
+ @Override
+ public void setFailedLoginNotBefore(int notBefore) {
+ loginFailure.setFailedLoginNotBefore(notBefore);
+ }
+
+ @Override
+ public int getNumFailures() {
+ return loginFailure.getNumFailures();
+ }
+
+ @Override
+ public void incrementFailures() {
+ loginFailure.incrementFailures();
+ }
+
+ @Override
+ public void clearFailures() {
+ loginFailure.clearFailures();
+ }
+
+ @Override
+ public long getLastFailure() {
+ return loginFailure.getLastFailure();
+ }
+
+ @Override
+ public void setLastFailure(long lastFailure) {
+ loginFailure.setLastFailure(lastFailure);
+ }
+
+ @Override
+ public String getLastIPFailure() {
+ return loginFailure.getLastIPFailure();
+ }
+
+ @Override
+ public void setLastIPFailure(String ip) {
+ loginFailure.setLastIPFailure(ip);
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserSessionAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserSessionAdapter.java
new file mode 100644
index 0000000..8b5a0d9
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserSessionAdapter.java
@@ -0,0 +1,100 @@
+package org.keycloak.models.hybrid;
+
+import org.keycloak.models.ClientModel;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.UserModel;
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.Session;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSessionAdapter implements UserSessionModel {
+
+ private HybridModelProvider provider;
+ private RealmModel realm;
+ private Session session;
+
+ UserSessionAdapter(HybridModelProvider provider, RealmModel realm, Session session) {
+ this.provider = provider;
+ this.realm = realm;
+ this.session = session;
+ }
+
+ Session getSession() {
+ return session;
+ }
+
+ @Override
+ public String getId() {
+ return session.getId();
+ }
+
+ @Override
+ public void setId(String id) {
+ session.setId(id);
+ }
+
+ @Override
+ public UserModel getUser() {
+ return provider.getUserById(session.getUser(), realm);
+ }
+
+ @Override
+ public void setUser(UserModel user) {
+ session.setUser(user.getId());
+ }
+
+ @Override
+ public String getIpAddress() {
+ return session.getIpAddress();
+ }
+
+ @Override
+ public void setIpAddress(String ipAddress) {
+ session.setIpAddress(ipAddress);
+ }
+
+ @Override
+ public int getStarted() {
+ return session.getStarted();
+ }
+
+ @Override
+ public void setStarted(int started) {
+ session.setStarted(started);
+ }
+
+ @Override
+ public int getLastSessionRefresh() {
+ return session.getLastSessionRefresh();
+ }
+
+ @Override
+ public void setLastSessionRefresh(int seconds) {
+ session.setLastSessionRefresh(seconds);
+ }
+
+ @Override
+ public void associateClient(ClientModel client) {
+ session.associateClient(client.getId());
+ }
+
+ @Override
+ public List<ClientModel> getClientAssociations() {
+ List<ClientModel> clients = new LinkedList<ClientModel>();
+ for (String id : session.getClientAssociations()) {
+ clients.add(realm.findClientById(id));
+ }
+ return clients;
+ }
+
+ @Override
+ public void removeAssociatedClient(ClientModel client) {
+ session.removeAssociatedClient(client.getId());
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/Application.java b/model/hybrid/src/main/java/org/keycloak/models/realms/Application.java
new file mode 100755
index 0000000..bf6afb0
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/Application.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.realms;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Application extends RoleContainer, Client {
+ void updateApplication();
+
+ String getName();
+
+ void setName(String name);
+
+ boolean isSurrogateAuthRequired();
+
+ void setSurrogateAuthRequired(boolean surrogateAuthRequired);
+
+ String getManagementUrl();
+
+ void setManagementUrl(String url);
+
+ String getBaseUrl();
+
+ void setBaseUrl(String url);
+
+ List<String> getDefaultRoles();
+
+ void addDefaultRole(String name);
+
+ void updateDefaultRoles(String[] defaultRoles);
+
+ Set<Role> getApplicationScopeMappings(Client client);
+
+ boolean isBearerOnly();
+ void setBearerOnly(boolean only);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/Client.java b/model/hybrid/src/main/java/org/keycloak/models/realms/Client.java
new file mode 100755
index 0000000..f7d6a6a
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/Client.java
@@ -0,0 +1,76 @@
+package org.keycloak.models.realms;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Client {
+ /**
+ * Internal database key
+ *
+ * @return
+ */
+ String getId();
+
+ /**
+ * String exposed to outside world
+ *
+ * @return
+ */
+ String getClientId();
+
+ long getAllowedClaimsMask();
+
+ void setAllowedClaimsMask(long mask);
+
+ Set<String> getWebOrigins();
+
+ void setWebOrigins(Set<String> webOrigins);
+
+ void addWebOrigin(String webOrigin);
+
+ void removeWebOrigin(String webOrigin);
+
+ Set<String> getRedirectUris();
+
+ void setRedirectUris(Set<String> redirectUris);
+
+ void addRedirectUri(String redirectUri);
+
+ void removeRedirectUri(String redirectUri);
+
+
+ boolean isEnabled();
+
+ void setEnabled(boolean enabled);
+
+ boolean validateSecret(String secret);
+ String getSecret();
+ public void setSecret(String secret);
+
+ boolean isPublicClient();
+ void setPublicClient(boolean flag);
+
+ boolean isDirectGrantsOnly();
+ void setDirectGrantsOnly(boolean flag);
+
+ Set<Role> getScopeMappings();
+ void addScopeMapping(Role role);
+ void deleteScopeMapping(Role role);
+ Set<Role> getRealmScopeMappings();
+
+
+ Realm getRealm();
+
+ /**
+ * Time in seconds since epoc
+ *
+ * @return
+ */
+ int getNotBefore();
+
+ void setNotBefore(int notBefore);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/OAuthClient.java b/model/hybrid/src/main/java/org/keycloak/models/realms/OAuthClient.java
new file mode 100755
index 0000000..74f4cfd
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/OAuthClient.java
@@ -0,0 +1,11 @@
+package org.keycloak.models.realms;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface OAuthClient extends Client {
+
+ void setClientId(String id);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/Realm.java b/model/hybrid/src/main/java/org/keycloak/models/realms/Realm.java
new file mode 100755
index 0000000..5f1d6e8
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/Realm.java
@@ -0,0 +1,208 @@
+package org.keycloak.models.realms;
+
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.RequiredCredentialModel;
+
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Realm extends RoleContainer {
+
+ String getId();
+
+ String getName();
+
+ void setName(String name);
+
+ boolean isEnabled();
+
+ void setEnabled(boolean enabled);
+
+ boolean isSslNotRequired();
+
+ void setSslNotRequired(boolean sslNotRequired);
+
+ boolean isRegistrationAllowed();
+
+ void setRegistrationAllowed(boolean registrationAllowed);
+
+ boolean isPasswordCredentialGrantAllowed();
+
+ void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed);
+
+ boolean isRememberMe();
+
+ void setRememberMe(boolean rememberMe);
+
+ //--- brute force settings
+ boolean isBruteForceProtected();
+ void setBruteForceProtected(boolean value);
+ int getMaxFailureWaitSeconds();
+ void setMaxFailureWaitSeconds(int val);
+ int getWaitIncrementSeconds();
+ void setWaitIncrementSeconds(int val);
+ int getMinimumQuickLoginWaitSeconds();
+ void setMinimumQuickLoginWaitSeconds(int val);
+ long getQuickLoginCheckMilliSeconds();
+ void setQuickLoginCheckMilliSeconds(long val);
+ int getMaxDeltaTimeSeconds();
+ void setMaxDeltaTimeSeconds(int val);
+ int getFailureFactor();
+ void setFailureFactor(int failureFactor);
+ //--- end brute force settings
+
+
+ boolean isVerifyEmail();
+
+ void setVerifyEmail(boolean verifyEmail);
+
+ boolean isResetPasswordAllowed();
+
+ void setResetPasswordAllowed(boolean resetPasswordAllowed);
+
+ int getSsoSessionIdleTimeout();
+ void setSsoSessionIdleTimeout(int seconds);
+
+ int getSsoSessionMaxLifespan();
+ void setSsoSessionMaxLifespan(int seconds);
+
+ int getAccessTokenLifespan();
+
+ void setAccessTokenLifespan(int seconds);
+
+ int getAccessCodeLifespan();
+
+ void setAccessCodeLifespan(int seconds);
+
+ int getAccessCodeLifespanUserAction();
+
+ void setAccessCodeLifespanUserAction(int seconds);
+
+ String getPublicKeyPem();
+
+ void setPublicKeyPem(String publicKeyPem);
+
+ String getPrivateKeyPem();
+
+ void setPrivateKeyPem(String privateKeyPem);
+
+ PublicKey getPublicKey();
+
+ void setPublicKey(PublicKey publicKey);
+
+ PrivateKey getPrivateKey();
+
+ void setPrivateKey(PrivateKey privateKey);
+
+ List<RequiredCredentialModel> getRequiredCredentials();
+
+ void addRequiredCredential(String cred);
+
+ PasswordPolicy getPasswordPolicy();
+
+ void setPasswordPolicy(PasswordPolicy policy);
+
+ List<String> getDefaultRoles();
+
+ void addDefaultRole(String name);
+
+ void updateDefaultRoles(String[] defaultRoles);
+
+ Client findClient(String clientId);
+
+ Map<String, Application> getApplicationNameMap();
+
+ List<Application> getApplications();
+
+ Application addApplication(String id, String name);
+
+ boolean removeApplication(Application application);
+
+ Application getApplicationByName(String name);
+
+ void updateRequiredCredentials(Set<String> creds);
+
+ boolean isSocial();
+
+ void setSocial(boolean social);
+
+ boolean isUpdateProfileOnInitialSocialLogin();
+
+ void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin);
+
+ OAuthClient addOAuthClient(String id, String name);
+
+ OAuthClient getOAuthClient(String name);
+ boolean removeOAuthClient(OAuthClient client);
+
+ List<OAuthClient> getOAuthClients();
+
+ Map<String, String> getSmtpConfig();
+
+ void setSmtpConfig(Map<String, String> smtpConfig);
+
+ Map<String, String> getSocialConfig();
+
+ void setSocialConfig(Map<String, String> socialConfig);
+
+ Map<String, String> getLdapServerConfig();
+
+ void setLdapServerConfig(Map<String, String> ldapServerConfig);
+
+ List<AuthenticationProviderModel> getAuthenticationProviders();
+
+ void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders);
+
+ String getLoginTheme();
+
+ void setLoginTheme(String name);
+
+ String getAccountTheme();
+
+ void setAccountTheme(String name);
+
+ String getAdminTheme();
+
+ void setAdminTheme(String name);
+
+ String getEmailTheme();
+
+ void setEmailTheme(String name);
+
+
+ /**
+ * Time in seconds since epoc
+ *
+ * @return
+ */
+ int getNotBefore();
+
+ void setNotBefore(int notBefore);
+
+ boolean removeRole(Role role);
+
+ boolean isAuditEnabled();
+
+ void setAuditEnabled(boolean enabled);
+
+ long getAuditExpiration();
+
+ void setAuditExpiration(long expiration);
+
+ Set<String> getAuditListeners();
+
+ void setAuditListeners(Set<String> listeners);
+
+ Application getMasterAdminApp();
+
+ void setMasterAdminApp(Application app);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/RealmProvider.java b/model/hybrid/src/main/java/org/keycloak/models/realms/RealmProvider.java
new file mode 100644
index 0000000..b762d67
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/RealmProvider.java
@@ -0,0 +1,29 @@
+package org.keycloak.models.realms;
+
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface RealmProvider extends Provider {
+
+ Realm createRealm(String name);
+ Realm createRealm(String id, String name);
+
+ Realm getRealm(String id);
+ Realm getRealmByName(String name);
+ List<Realm> getRealms();
+ boolean removeRealm(String id);
+
+ Role getRoleById(String id, String realm);
+ Application getApplicationById(String id, String realm);
+ OAuthClient getOAuthClientById(String id, String realm);
+
+ KeycloakTransaction getTransaction();
+
+ void close();
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/RealmProviderFactory.java b/model/hybrid/src/main/java/org/keycloak/models/realms/RealmProviderFactory.java
new file mode 100755
index 0000000..fb4dea3
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/RealmProviderFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.realms;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface RealmProviderFactory extends ProviderFactory<RealmProvider> {
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/RealmSpi.java b/model/hybrid/src/main/java/org/keycloak/models/realms/RealmSpi.java
new file mode 100644
index 0000000..9ec584f
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/RealmSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.realms;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class RealmSpi implements Spi {
+
+ @Override
+ public String getName() {
+ return "modelRealms";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return RealmProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return RealmProviderFactory.class;
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/Role.java b/model/hybrid/src/main/java/org/keycloak/models/realms/Role.java
new file mode 100755
index 0000000..431d1b6
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/Role.java
@@ -0,0 +1,31 @@
+package org.keycloak.models.realms;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface Role {
+
+ String getId();
+
+ String getName();
+
+ void setName(String name);
+
+ String getDescription();
+
+ void setDescription(String description);
+
+ boolean isComposite();
+
+ void addCompositeRole(Role role);
+
+ void removeCompositeRole(Role role);
+
+ Set<Role> getComposites();
+
+ RoleContainer getContainer();
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/realms/RoleContainer.java b/model/hybrid/src/main/java/org/keycloak/models/realms/RoleContainer.java
new file mode 100755
index 0000000..5d4c02c
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/realms/RoleContainer.java
@@ -0,0 +1,19 @@
+package org.keycloak.models.realms;
+
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface RoleContainer {
+
+ Role getRole(String name);
+
+ Set<Role> getRoles();
+
+ Role addRole(String id, String name);
+
+ boolean removeRole(Role role);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/sessions/LoginFailure.java b/model/hybrid/src/main/java/org/keycloak/models/sessions/LoginFailure.java
new file mode 100755
index 0000000..f0e7b24
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/sessions/LoginFailure.java
@@ -0,0 +1,29 @@
+package org.keycloak.models.sessions;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface LoginFailure {
+
+ String getUsername();
+
+ int getFailedLoginNotBefore();
+
+ void setFailedLoginNotBefore(int notBefore);
+
+ int getNumFailures();
+
+ void incrementFailures();
+
+ void clearFailures();
+
+ long getLastFailure();
+
+ void setLastFailure(long lastFailure);
+
+ String getLastIPFailure();
+
+ void setLastIPFailure(String ip);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/sessions/Session.java b/model/hybrid/src/main/java/org/keycloak/models/sessions/Session.java
new file mode 100755
index 0000000..722895d
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/sessions/Session.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.sessions;
+
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Session {
+
+ String getId();
+
+ void setId(String id);
+
+ String getUser();
+
+ void setUser(String user);
+
+ String getIpAddress();
+
+ void setIpAddress(String ipAddress);
+
+ int getStarted();
+
+ void setStarted(int started);
+
+ int getLastSessionRefresh();
+
+ void setLastSessionRefresh(int seconds);
+
+ void associateClient(String client);
+
+ List<String> getClientAssociations();
+
+ void removeAssociatedClient(String client);
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionProvider.java b/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionProvider.java
new file mode 100644
index 0000000..baf0396
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionProvider.java
@@ -0,0 +1,42 @@
+package org.keycloak.models.sessions;
+
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface SessionProvider extends Provider {
+
+ LoginFailure getUserLoginFailure(String username, String realm);
+
+ LoginFailure addUserLoginFailure(String username, String realm);
+
+ List<LoginFailure> getAllUserLoginFailures(String realm);
+
+ Session createUserSession(String realm, String id, String user, String ipAddress);
+
+ Session getUserSession(String id, String realm);
+
+ List<Session> getUserSessionsByUser(String user, String realm);
+
+ Set<Session> getUserSessionsByClient(String realm, String client);
+
+ int getActiveUserSessions(String realm, String client);
+
+ void removeUserSession(Session session);
+
+ void removeUserSessions(String realm, String user);
+
+ void removeExpiredUserSessions(String realm, long refreshTimeout, long sessionTimeout);
+
+ void removeUserSessions(String realm);
+
+ KeycloakTransaction getTransaction();
+
+ void close();
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionProviderFactory.java b/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionProviderFactory.java
new file mode 100755
index 0000000..08e2051
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionProviderFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.sessions;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface SessionProviderFactory extends ProviderFactory<SessionProvider> {
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionSpi.java b/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionSpi.java
new file mode 100644
index 0000000..cf6c7fc
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/sessions/SessionSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.sessions;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SessionSpi implements Spi {
+
+ @Override
+ public String getName() {
+ return "modelSessions";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return SessionProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return SessionProviderFactory.class;
+ }
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/Attributes.java b/model/hybrid/src/main/java/org/keycloak/models/users/Attributes.java
new file mode 100644
index 0000000..c341ab0
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/Attributes.java
@@ -0,0 +1,12 @@
+package org.keycloak.models.users;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface Attributes {
+
+ String EMAIL_VERIFIED = "keycloak.emailVerified";
+ String TOTP_ENABLED = "keycloak.totpEnabled";
+ String REQUIRED_ACTIONS = "keycloak.requiredActions";
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/Credentials.java b/model/hybrid/src/main/java/org/keycloak/models/users/Credentials.java
new file mode 100644
index 0000000..57368bc
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/Credentials.java
@@ -0,0 +1,72 @@
+package org.keycloak.models.users;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class Credentials {
+
+ private byte[] salt;
+ private String type;
+ private String value;
+ private String device;
+ private int hashIterations;
+
+ public Credentials(String type, String value) {
+ this.type = type;
+ this.value = value;
+ }
+
+ public Credentials(String type, String value, String device) {
+ this.type = type;
+ this.value = value;
+ this.device = device;
+ }
+
+ public Credentials(String type, byte[] salt, String value, int hashIterations, String device) {
+ this.salt = salt;
+ this.type = type;
+ this.value = value;
+ this.hashIterations = hashIterations;
+ this.device = device;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public void setSalt(byte[] salt) {
+ this.salt = salt;
+ }
+
+ public int getHashIterations() {
+ return hashIterations;
+ }
+
+ public void setHashIterations(int hashIterations) {
+ this.hashIterations = hashIterations;
+ }
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/Feature.java b/model/hybrid/src/main/java/org/keycloak/models/users/Feature.java
new file mode 100644
index 0000000..395fb03
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/Feature.java
@@ -0,0 +1,12 @@
+package org.keycloak.models.users;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public enum Feature {
+
+ READ_CREDENTIALS,
+ UPDATE_CREDENTIALS,
+ VERIFY_CREDENTIALS;
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/User.java b/model/hybrid/src/main/java/org/keycloak/models/users/User.java
new file mode 100755
index 0000000..90f2880
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/User.java
@@ -0,0 +1,58 @@
+package org.keycloak.models.users;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface User {
+
+ public static final String USERNAME = "username";
+ public static final String LAST_NAME = "lastName";
+ public static final String FIRST_NAME = "firstName";
+ public static final String EMAIL = "email";
+
+ String getId();
+
+ boolean isEnabled();
+
+ void setEnabled(boolean enabled);
+
+ String getUsername();
+
+ void setUsername(String username);
+
+ String getFirstName();
+
+ void setFirstName(String firstName);
+
+ String getLastName();
+
+ void setLastName(String lastName);
+
+ String getEmail();
+
+ void setEmail(String email);
+
+ String getAttribute(String name);
+
+ Map<String, String> getAttributes();
+
+ void setAttribute(String name, String value);
+
+ void removeAttribute(String name);
+
+ List<Credentials> getCredentials();
+
+ void updateCredential(Credentials cred);
+
+ Set<String> getRoleMappings();
+
+ void grantRole(String role);
+
+ void deleteRoleMapping(String role);
+
+}
\ No newline at end of file
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/UserProvider.java b/model/hybrid/src/main/java/org/keycloak/models/users/UserProvider.java
new file mode 100644
index 0000000..de53736
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/UserProvider.java
@@ -0,0 +1,46 @@
+package org.keycloak.models.users;
+
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.provider.Provider;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface UserProvider extends Provider {
+
+ KeycloakTransaction getTransaction();
+
+ User addUser(String id, String username, Set<String> roles, String realm);
+
+ boolean removeUser(String name, String realm);
+
+ User getUserById(String id, String realm);
+ User getUserByUsername(String username, String realm);
+ User getUserByEmail(String email, String realm);
+ User getUserByAttribute(String name, String value, String realm);
+
+ List<User> getUsers(String realm);
+ List<User> searchForUser(String search, String realm);
+ List<User> searchForUserByAttributes(Map<String, String> attributes, String realm);
+
+ /**
+ * Returns features supported by the provider. A provider is required to at least support one of verifying credentials
+ * or reading credentials.
+ *
+ * @param feature
+ * @return
+ */
+ boolean supports(Feature feature);
+
+ boolean verifyCredentials(User user, Credentials... credentials);
+
+ void onRealmRemoved(String realm);
+ void onRoleRemoved(String role);
+
+ void close();
+
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/UserProviderFactory.java b/model/hybrid/src/main/java/org/keycloak/models/users/UserProviderFactory.java
new file mode 100755
index 0000000..e2f7c46
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/UserProviderFactory.java
@@ -0,0 +1,9 @@
+package org.keycloak.models.users;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public interface UserProviderFactory extends ProviderFactory<UserProvider> {
+}
diff --git a/model/hybrid/src/main/java/org/keycloak/models/users/UserSpi.java b/model/hybrid/src/main/java/org/keycloak/models/users/UserSpi.java
new file mode 100644
index 0000000..e3d8e23
--- /dev/null
+++ b/model/hybrid/src/main/java/org/keycloak/models/users/UserSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.users;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSpi implements Spi {
+
+ @Override
+ public String getName() {
+ return "modelUsers";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return UserProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return UserProviderFactory.class;
+ }
+
+}
diff --git a/model/hybrid/src/main/resources/META-INF/services/org.keycloak.models.ModelProviderFactory b/model/hybrid/src/main/resources/META-INF/services/org.keycloak.models.ModelProviderFactory
new file mode 100644
index 0000000..4fdf853
--- /dev/null
+++ b/model/hybrid/src/main/resources/META-INF/services/org.keycloak.models.ModelProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.hybrid.HybridModelProviderFactory
\ No newline at end of file
diff --git a/model/hybrid/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/hybrid/src/main/resources/META-INF/services/org.keycloak.provider.Spi
new file mode 100644
index 0000000..6f3050e
--- /dev/null
+++ b/model/hybrid/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -0,0 +1,3 @@
+org.keycloak.models.realms.RealmSpi
+org.keycloak.models.sessions.SessionSpi
+org.keycloak.models.users.UserSpi
\ No newline at end of file
model/pom.xml 7(+7 -0)
diff --git a/model/pom.xml b/model/pom.xml
index 850a21f..5051547 100755
--- a/model/pom.xml
+++ b/model/pom.xml
@@ -31,5 +31,12 @@
<module>jpa</module>
<module>mongo</module>
<module>tests</module>
+
+ <module>hybrid</module>
+ <module>realms-jpa</module>
+ <module>users-jpa</module>
+ <module>sessions-mem</module>
+ <module>sessions-jpa</module>
+ <module>tests-hybrid</module>
</modules>
</project>
model/realms-jpa/pom.xml 45(+45 -0)
diff --git a/model/realms-jpa/pom.xml b/model/realms-jpa/pom.xml
new file mode 100755
index 0000000..09413e8
--- /dev/null
+++ b/model/realms-jpa/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-model-realms-jpa</artifactId>
+ <name>Keycloak Model Realms JPA</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.0-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <version>${hibernate.entitymanager.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/ApplicationAdapter.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/ApplicationAdapter.java
new file mode 100755
index 0000000..e44c7b1
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/ApplicationAdapter.java
@@ -0,0 +1,258 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.realms.Client;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.realms.RoleContainer;
+import org.keycloak.models.realms.jpa.entities.ScopeMappingEntity;
+import org.keycloak.models.realms.jpa.entities.ApplicationEntity;
+import org.keycloak.models.realms.jpa.entities.RoleEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class ApplicationAdapter extends ClientAdapter implements Application {
+
+ protected EntityManager em;
+ protected ApplicationEntity applicationEntity;
+
+ public ApplicationAdapter(RealmProvider provider, EntityManager em, ApplicationEntity applicationEntity) {
+ super(provider, applicationEntity, em);
+ this.em = em;
+ this.applicationEntity = applicationEntity;
+ }
+
+ @Override
+ public void updateApplication() {
+ em.flush();
+ }
+
+ @Override
+ public String getName() {
+ return entity.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ entity.setName(name);
+ }
+
+ @Override
+ public boolean isSurrogateAuthRequired() {
+ return applicationEntity.isSurrogateAuthRequired();
+ }
+
+ @Override
+ public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+ applicationEntity.setSurrogateAuthRequired(surrogateAuthRequired);
+ }
+
+ @Override
+ public String getManagementUrl() {
+ return applicationEntity.getManagementUrl();
+ }
+
+ @Override
+ public void setManagementUrl(String url) {
+ applicationEntity.setManagementUrl(url);
+ }
+
+ @Override
+ public String getBaseUrl() {
+ return applicationEntity.getBaseUrl();
+ }
+
+ @Override
+ public void setBaseUrl(String url) {
+ applicationEntity.setBaseUrl(url);
+ }
+
+ @Override
+ public boolean isBearerOnly() {
+ return applicationEntity.isBearerOnly();
+ }
+
+ @Override
+ public void setBearerOnly(boolean only) {
+ applicationEntity.setBearerOnly(only);
+ }
+
+ @Override
+ public boolean isDirectGrantsOnly() {
+ return false; // applications can't be grant only
+ }
+
+ @Override
+ public void setDirectGrantsOnly(boolean flag) {
+ // applications can't be grant only
+ }
+
+ @Override
+ public Role getRole(String name) {
+ TypedQuery<RoleEntity> query = em.createNamedQuery("getAppRoleByName", RoleEntity.class);
+ query.setParameter("name", name);
+ query.setParameter("application", entity);
+ List<RoleEntity> roles = query.getResultList();
+ if (roles.size() == 0) return null;
+ return new RoleAdapter(provider, em, roles.get(0));
+ }
+
+ @Override
+ public Role addRole(String id, String name) {
+ RoleEntity roleEntity = new RoleEntity();
+ roleEntity.setId(id);
+ roleEntity.setName(name);
+ roleEntity.setApplication(applicationEntity);
+ roleEntity.setApplicationRole(true);
+ roleEntity.setRealmId(entity.getRealm().getId());
+ em.persist(roleEntity);
+ applicationEntity.getRoles().add(roleEntity);
+ em.flush();
+ return new RoleAdapter(provider, em, roleEntity);
+ }
+
+ @Override
+ public boolean removeRole(Role Role) {
+ RoleAdapter roleAdapter = (RoleAdapter) Role;
+ if (Role == null) {
+ return false;
+ }
+ if (!roleAdapter.getContainer().equals(this)) return false;
+
+ if (!roleAdapter.getRole().isApplicationRole()) return false;
+
+ RoleEntity role = roleAdapter.getRole();
+
+ applicationEntity.getRoles().remove(role);
+ applicationEntity.getDefaultRoles().remove(role);
+ em.createNativeQuery("delete from CompositeRole where childRole = :role").setParameter("role", role).executeUpdate();
+ em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", role).executeUpdate();
+ role.setApplication(null);
+ em.flush();
+ em.remove(role);
+ em.flush();
+
+ return true;
+ }
+
+ @Override
+ public Set<Role> getRoles() {
+ Set<Role> list = new HashSet<Role>();
+ Collection<RoleEntity> roles = applicationEntity.getRoles();
+ if (roles == null) return list;
+ for (RoleEntity entity : roles) {
+ list.add(new RoleAdapter(provider, em, entity));
+ }
+ return list;
+ }
+
+ @Override
+ public Set<Role> getApplicationScopeMappings(Client client) {
+ Set<Role> roleMappings = client.getScopeMappings();
+
+ Set<Role> appRoles = new HashSet<Role>();
+ for (Role role : roleMappings) {
+ RoleContainer container = role.getContainer();
+ if (container instanceof Realm) {
+ } else {
+ Application app = (Application)container;
+ if (app.getId().equals(getId())) {
+ appRoles.add(role);
+ }
+ }
+ }
+
+ return appRoles;
+ }
+
+
+
+
+ @Override
+ public List<String> getDefaultRoles() {
+ Collection<RoleEntity> entities = applicationEntity.getDefaultRoles();
+ List<String> roles = new ArrayList<String>();
+ if (entities == null) return roles;
+ for (RoleEntity entity : entities) {
+ roles.add(entity.getName());
+ }
+ return roles;
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ Role role = getRole(name);
+ Collection<RoleEntity> entities = applicationEntity.getDefaultRoles();
+ for (RoleEntity entity : entities) {
+ if (entity.getId().equals(role.getId())) {
+ return;
+ }
+ }
+ entities.add(((RoleAdapter) role).getRole());
+ em.flush();
+ }
+
+ public static boolean contains(String str, String[] array) {
+ for (String s : array) {
+ if (str.equals(s)) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ Collection<RoleEntity> entities = applicationEntity.getDefaultRoles();
+ Set<String> already = new HashSet<String>();
+ List<RoleEntity> remove = new ArrayList<RoleEntity>();
+ for (RoleEntity rel : entities) {
+ if (!contains(rel.getName(), defaultRoles)) {
+ remove.add(rel);
+ } else {
+ already.add(rel.getName());
+ }
+ }
+ for (RoleEntity entity : remove) {
+ entities.remove(entity);
+ }
+ em.flush();
+ for (String roleName : defaultRoles) {
+ if (!already.contains(roleName)) {
+ addDefaultRole(roleName);
+ }
+ }
+ em.flush();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof Application)) return false;
+
+ Application that = (Application) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+ public String toString() {
+ return getName();
+ }
+
+ ApplicationEntity getJpaEntity() {
+ return applicationEntity;
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/ClientAdapter.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/ClientAdapter.java
new file mode 100755
index 0000000..660646e
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/ClientAdapter.java
@@ -0,0 +1,225 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.realms.Client;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.realms.RoleContainer;
+import org.keycloak.models.realms.jpa.entities.ScopeMappingEntity;
+import org.keycloak.models.realms.jpa.entities.ClientEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public abstract class ClientAdapter implements Client {
+ protected RealmProvider provider;
+ protected ClientEntity entity;
+ protected EntityManager em;
+
+ public ClientAdapter(RealmProvider provider, ClientEntity entity, EntityManager em) {
+ this.provider = provider;
+ this.entity = entity;
+ this.em = em;
+ }
+
+ public ClientEntity getEntity() {
+ return entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public Realm getRealm() {
+ return new RealmAdapter(provider, em, entity.getRealm());
+ }
+
+ @Override
+ public String getClientId() {
+ return entity.getName();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return entity.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ entity.setEnabled(enabled);
+ }
+
+ @Override
+ public long getAllowedClaimsMask() {
+ return entity.getAllowedClaimsMask();
+ }
+
+ @Override
+ public void setAllowedClaimsMask(long mask) {
+ entity.setAllowedClaimsMask(mask);
+ }
+
+ @Override
+ public boolean isPublicClient() {
+ return entity.isPublicClient();
+ }
+
+ @Override
+ public void setPublicClient(boolean flag) {
+ entity.setPublicClient(flag);
+ }
+
+ @Override
+ public Set<String> getWebOrigins() {
+ Set<String> result = new HashSet<String>();
+ result.addAll(entity.getWebOrigins());
+ return result;
+ }
+
+
+
+ @Override
+ public void setWebOrigins(Set<String> webOrigins) {
+ entity.setWebOrigins(webOrigins);
+ }
+
+ @Override
+ public void addWebOrigin(String webOrigin) {
+ entity.getWebOrigins().add(webOrigin);
+ }
+
+ @Override
+ public void removeWebOrigin(String webOrigin) {
+ entity.getWebOrigins().remove(webOrigin);
+ }
+
+ @Override
+ public Set<String> getRedirectUris() {
+ Set<String> result = new HashSet<String>();
+ result.addAll(entity.getRedirectUris());
+ return result;
+ }
+
+ @Override
+ public void setRedirectUris(Set<String> redirectUris) {
+ entity.setRedirectUris(redirectUris);
+ }
+
+ @Override
+ public void addRedirectUri(String redirectUri) {
+ entity.getRedirectUris().add(redirectUri);
+ }
+
+ @Override
+ public void removeRedirectUri(String redirectUri) {
+ entity.getRedirectUris().remove(redirectUri);
+ }
+
+ @Override
+ public String getSecret() {
+ return entity.getSecret();
+ }
+
+ @Override
+ public void setSecret(String secret) {
+ entity.setSecret(secret);
+ }
+
+ @Override
+ public boolean validateSecret(String secret) {
+ return secret.equals(entity.getSecret());
+ }
+
+ @Override
+ public int getNotBefore() {
+ return entity.getNotBefore();
+ }
+
+ @Override
+ public void setNotBefore(int notBefore) {
+ entity.setNotBefore(notBefore);
+ }
+
+ @Override
+ public Set<Role> getRealmScopeMappings() {
+ Set<Role> roleMappings = getScopeMappings();
+
+ Set<Role> appRoles = new HashSet<Role>();
+ for (Role role : roleMappings) {
+ RoleContainer container = role.getContainer();
+ if (container instanceof Realm) {
+ if (((Realm) container).getId().equals(entity.getRealm().getId())) {
+ appRoles.add(role);
+ }
+ }
+ }
+
+ return appRoles;
+ }
+
+
+
+ @Override
+ public Set<Role> getScopeMappings() {
+ TypedQuery<String> query = em.createNamedQuery("clientScopeMappingIds", String.class);
+ query.setParameter("client", getEntity());
+ List<String> ids = query.getResultList();
+ Set<Role> roles = new HashSet<Role>();
+ for (String roleId : ids) {
+ Role role = provider.getRoleById(roleId, entity.getRealm().getId());
+ if (role == null) continue;
+ roles.add(role);
+ }
+ return roles;
+ }
+
+ @Override
+ public void addScopeMapping(Role role) {
+ ScopeMappingEntity entity = new ScopeMappingEntity();
+ entity.setClient(getEntity());
+ entity.setRole(((RoleAdapter) role).getRole());
+ em.persist(entity);
+ em.flush();
+ em.detach(entity);
+ }
+
+ @Override
+ public void deleteScopeMapping(Role role) {
+ TypedQuery<ScopeMappingEntity> query = getRealmScopeMappingQuery((RoleAdapter) role);
+ List<ScopeMappingEntity> results = query.getResultList();
+ if (results.size() == 0) return;
+ for (ScopeMappingEntity entity : results) {
+ em.remove(entity);
+ }
+ }
+
+ protected TypedQuery<ScopeMappingEntity> getRealmScopeMappingQuery(RoleAdapter role) {
+ TypedQuery<ScopeMappingEntity> query = em.createNamedQuery("hasScope", ScopeMappingEntity.class);
+ query.setParameter("client", getEntity());
+ query.setParameter("role", role.getRole());
+ return query;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!this.getClass().equals(o.getClass())) return false;
+
+ ClientAdapter that = (ClientAdapter) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return entity.getId().hashCode();
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ApplicationEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ApplicationEntity.java
new file mode 100755
index 0000000..6093fbc
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ApplicationEntity.java
@@ -0,0 +1,77 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.JoinTable;
+import javax.persistence.OneToMany;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+public class ApplicationEntity extends ClientEntity {
+
+ private boolean surrogateAuthRequired;
+ private String baseUrl;
+ private String managementUrl;
+ private boolean bearerOnly;
+
+ @OneToMany(fetch = FetchType.EAGER, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "application")
+ Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
+
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ @JoinTable(name="ApplicationDefaultRoles")
+ Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
+
+ public boolean isSurrogateAuthRequired() {
+ return surrogateAuthRequired;
+ }
+
+ public void setSurrogateAuthRequired(boolean surrogateAuthRequired) {
+ this.surrogateAuthRequired = surrogateAuthRequired;
+ }
+
+ public String getBaseUrl() {
+ return baseUrl;
+ }
+
+ public void setBaseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ public String getManagementUrl() {
+ return managementUrl;
+ }
+
+ public void setManagementUrl(String managementUrl) {
+ this.managementUrl = managementUrl;
+ }
+
+ public Collection<RoleEntity> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Collection<RoleEntity> roles) {
+ this.roles = roles;
+ }
+
+ public Collection<RoleEntity> getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
+
+ public boolean isBearerOnly() {
+ return bearerOnly;
+ }
+
+ public void setBearerOnly(boolean bearerOnly) {
+ this.bearerOnly = bearerOnly;
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/AuthenticationProviderEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/AuthenticationProviderEntity.java
new file mode 100644
index 0000000..261a10b
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/AuthenticationProviderEntity.java
@@ -0,0 +1,79 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@Entity
+@Table(name="AuthProviderEntity")
+public class AuthenticationProviderEntity {
+
+ @Id
+ @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.realms.jpa.utils.JpaIdGenerator")
+ @GeneratedValue(generator = "keycloak_generator")
+ protected String id;
+
+ private String providerName;
+ private boolean passwordUpdateSupported;
+ private int priority;
+
+ @ElementCollection
+ @MapKeyColumn(name="name")
+ @Column(name="value")
+ @CollectionTable(name="AuthProviderEntity_cfg", joinColumns = {
+ @JoinColumn(name = "AuthProviderEntity_id")
+ })
+ private Map<String, String> config;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public boolean isPasswordUpdateSupported() {
+ return passwordUpdateSupported;
+ }
+
+ public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
+ this.passwordUpdateSupported = passwordUpdateSupported;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ClientEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ClientEntity.java
new file mode 100755
index 0000000..05eb5e0
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ClientEntity.java
@@ -0,0 +1,126 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.Inheritance;
+import javax.persistence.InheritanceType;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
+@Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"realm", "name"})})
+public abstract class ClientEntity {
+ @Id
+ private String id;
+ @Column(name = "name")
+ private String name;
+ private boolean enabled;
+ private String secret;
+ private long allowedClaimsMask;
+ private int notBefore;
+ private boolean publicClient;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "realm")
+ protected RealmEntity realm;
+
+ @ElementCollection
+ @CollectionTable
+ protected Set<String> webOrigins = new HashSet<String>();
+ @ElementCollection
+ @CollectionTable
+ protected Set<String> redirectUris = new HashSet<String>();
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public long getAllowedClaimsMask() {
+ return allowedClaimsMask;
+ }
+
+ public void setAllowedClaimsMask(long allowedClaimsMask) {
+ this.allowedClaimsMask = allowedClaimsMask;
+ }
+
+ public Set<String> getWebOrigins() {
+ return webOrigins;
+ }
+
+ public void setWebOrigins(Set<String> webOrigins) {
+ this.webOrigins = webOrigins;
+ }
+
+ public Set<String> getRedirectUris() {
+ return redirectUris;
+ }
+
+ public void setRedirectUris(Set<String> redirectUris) {
+ this.redirectUris = redirectUris;
+ }
+
+ public String getSecret() {
+ return secret;
+ }
+
+ public void setSecret(String secret) {
+ this.secret = secret;
+ }
+
+ public int getNotBefore() {
+ return notBefore;
+ }
+
+ public void setNotBefore(int notBefore) {
+ this.notBefore = notBefore;
+ }
+
+ public boolean isPublicClient() {
+ return publicClient;
+ }
+
+ public void setPublicClient(boolean publicClient) {
+ this.publicClient = publicClient;
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/OAuthClientEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/OAuthClientEntity.java
new file mode 100755
index 0000000..55e351a
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/OAuthClientEntity.java
@@ -0,0 +1,27 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="findOAuthClientByName", query="select o from OAuthClientEntity o where o.name=:name and o.realm = :realm"),
+ @NamedQuery(name="findOAuthClientByRealm", query="select o from OAuthClientEntity o where o.realm = :realm")
+
+})
+@Entity
+public class OAuthClientEntity extends ClientEntity {
+ protected boolean directGrantsOnly;
+
+ public boolean isDirectGrantsOnly() {
+ return directGrantsOnly;
+ }
+
+ public void setDirectGrantsOnly(boolean directGrantsOnly) {
+ this.directGrantsOnly = directGrantsOnly;
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RealmEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RealmEntity.java
new file mode 100755
index 0000000..78c36d4
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RealmEntity.java
@@ -0,0 +1,479 @@
+package org.keycloak.models.realms.jpa.entities;
+
+
+import javax.persistence.CascadeType;
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinTable;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@NamedQueries({
+ @NamedQuery(name="getAllRealms", query="select realm from RealmEntity realm"),
+ @NamedQuery(name="getRealmByName", query="select realm from RealmEntity realm where realm.name = :name"),
+})
+public class RealmEntity {
+ @Id
+ protected String id;
+
+ @Column(unique = true)
+ protected String name;
+
+ protected boolean enabled;
+ protected boolean sslNotRequired;
+ protected boolean registrationAllowed;
+ protected boolean passwordCredentialGrantAllowed;
+ protected boolean verifyEmail;
+ protected boolean resetPasswordAllowed;
+ protected boolean social;
+ protected boolean rememberMe;
+ //--- brute force settings
+ protected boolean bruteForceProtected;
+ protected int maxFailureWaitSeconds;
+ protected int minimumQuickLoginWaitSeconds;
+ protected int waitIncrementSeconds;
+ protected long quickLoginCheckMilliSeconds;
+ protected int maxDeltaTimeSeconds;
+ protected int failureFactor;
+ //--- end brute force settings
+
+
+ @Column(name="updateProfileOnInitSocLogin")
+ protected boolean updateProfileOnInitialSocialLogin;
+ protected String passwordPolicy;
+
+ private int ssoSessionIdleTimeout;
+ private int ssoSessionMaxLifespan;
+ protected int accessTokenLifespan;
+ protected int accessCodeLifespan;
+ protected int accessCodeLifespanUserAction;
+ protected int notBefore;
+
+ @Column(length = 2048)
+ protected String publicKeyPem;
+ @Column(length = 2048)
+ protected String privateKeyPem;
+
+ protected String loginTheme;
+ protected String accountTheme;
+ protected String adminTheme;
+ protected String emailTheme;
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ @JoinTable(name="User_RequiredCreds")
+ Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
+
+
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ @JoinTable(name="AuthProviders")
+ List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
+
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
+
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
+ Collection<RoleEntity> roles = new ArrayList<RoleEntity>();
+
+ @ElementCollection
+ @MapKeyColumn(name="name")
+ @Column(name="value")
+ @CollectionTable
+ protected Map<String, String> smtpConfig = new HashMap<String, String>();
+
+ @ElementCollection
+ @MapKeyColumn(name="name")
+ @Column(name="value")
+ @CollectionTable
+ protected Map<String, String> socialConfig = new HashMap<String, String>();
+
+ @ElementCollection
+ @MapKeyColumn(name="name")
+ @Column(name="value")
+ @CollectionTable
+ protected Map<String, String> ldapServerConfig = new HashMap<String, String>();
+
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ @JoinTable(name="RealmDefaultRoles")
+ protected Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
+
+ protected boolean auditEnabled;
+ protected long auditExpiration;
+
+ @ElementCollection
+ protected Set<String> auditListeners= new HashSet<String>();
+
+ @OneToOne
+ protected ApplicationEntity masterAdminApp;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public boolean isSslNotRequired() {
+ return sslNotRequired;
+ }
+
+ public void setSslNotRequired(boolean sslNotRequired) {
+ this.sslNotRequired = sslNotRequired;
+ }
+
+ public boolean isPasswordCredentialGrantAllowed() {
+ return passwordCredentialGrantAllowed;
+ }
+
+ public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
+ this.passwordCredentialGrantAllowed = passwordCredentialGrantAllowed;
+ }
+
+ public boolean isRegistrationAllowed() {
+ return registrationAllowed;
+ }
+
+ public void setRegistrationAllowed(boolean registrationAllowed) {
+ this.registrationAllowed = registrationAllowed;
+ }
+
+ public boolean isRememberMe() {
+ return rememberMe;
+ }
+
+ public void setRememberMe(boolean rememberMe) {
+ this.rememberMe = rememberMe;
+ }
+
+ public boolean isVerifyEmail() {
+ return verifyEmail;
+ }
+
+ public void setVerifyEmail(boolean verifyEmail) {
+ this.verifyEmail = verifyEmail;
+ }
+
+ public boolean isResetPasswordAllowed() {
+ return resetPasswordAllowed;
+ }
+
+ public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
+ this.resetPasswordAllowed = resetPasswordAllowed;
+ }
+
+ public boolean isSocial() {
+ return social;
+ }
+
+ public void setSocial(boolean social) {
+ this.social = social;
+ }
+
+ public boolean isUpdateProfileOnInitialSocialLogin() {
+ return updateProfileOnInitialSocialLogin;
+ }
+
+ public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
+ this.updateProfileOnInitialSocialLogin = updateProfileOnInitialSocialLogin;
+ }
+
+ public int getSsoSessionIdleTimeout() {
+ return ssoSessionIdleTimeout;
+ }
+
+ public void setSsoSessionIdleTimeout(int ssoSessionIdleTimeout) {
+ this.ssoSessionIdleTimeout = ssoSessionIdleTimeout;
+ }
+
+ public int getSsoSessionMaxLifespan() {
+ return ssoSessionMaxLifespan;
+ }
+
+ public void setSsoSessionMaxLifespan(int ssoSessionMaxLifespan) {
+ this.ssoSessionMaxLifespan = ssoSessionMaxLifespan;
+ }
+
+ public int getAccessTokenLifespan() {
+ return accessTokenLifespan;
+ }
+
+ public void setAccessTokenLifespan(int accessTokenLifespan) {
+ this.accessTokenLifespan = accessTokenLifespan;
+ }
+
+ public int getAccessCodeLifespan() {
+ return accessCodeLifespan;
+ }
+
+ public void setAccessCodeLifespan(int accessCodeLifespan) {
+ this.accessCodeLifespan = accessCodeLifespan;
+ }
+
+ public int getAccessCodeLifespanUserAction() {
+ return accessCodeLifespanUserAction;
+ }
+
+ public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
+ this.accessCodeLifespanUserAction = accessCodeLifespanUserAction;
+ }
+
+ public String getPublicKeyPem() {
+ return publicKeyPem;
+ }
+
+ public void setPublicKeyPem(String publicKeyPem) {
+ this.publicKeyPem = publicKeyPem;
+ }
+
+ public String getPrivateKeyPem() {
+ return privateKeyPem;
+ }
+
+ public void setPrivateKeyPem(String privateKeyPem) {
+ this.privateKeyPem = privateKeyPem;
+ }
+
+ public Collection<RequiredCredentialEntity> getRequiredCredentials() {
+ return requiredCredentials;
+ }
+
+ public void setRequiredCredentials(Collection<RequiredCredentialEntity> requiredCredentials) {
+ this.requiredCredentials = requiredCredentials;
+ }
+
+ public List<AuthenticationProviderEntity> getAuthenticationProviders() {
+ return authenticationProviders;
+ }
+
+ public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
+ this.authenticationProviders = authenticationProviders;
+ }
+
+ public Collection<ApplicationEntity> getApplications() {
+ return applications;
+ }
+
+ public void setApplications(Collection<ApplicationEntity> applications) {
+ this.applications = applications;
+ }
+
+ public Collection<RoleEntity> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(Collection<RoleEntity> roles) {
+ this.roles = roles;
+ }
+
+ public void addRole(RoleEntity role) {
+ if (roles == null) {
+ roles = new ArrayList<RoleEntity>();
+ }
+ roles.add(role);
+ }
+
+ public Map<String, String> getSmtpConfig() {
+ return smtpConfig;
+ }
+
+ public void setSmtpConfig(Map<String, String> smtpConfig) {
+ this.smtpConfig = smtpConfig;
+ }
+
+ public Map<String, String> getSocialConfig() {
+ return socialConfig;
+ }
+
+ public void setSocialConfig(Map<String, String> socialConfig) {
+ this.socialConfig = socialConfig;
+ }
+
+ public Map<String, String> getLdapServerConfig() {
+ return ldapServerConfig;
+ }
+
+ public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+ this.ldapServerConfig = ldapServerConfig;
+ }
+
+ public Collection<RoleEntity> getDefaultRoles() {
+ return defaultRoles;
+ }
+
+ public void setDefaultRoles(Collection<RoleEntity> defaultRoles) {
+ this.defaultRoles = defaultRoles;
+ }
+
+ public String getPasswordPolicy() {
+ return passwordPolicy;
+ }
+
+ public void setPasswordPolicy(String passwordPolicy) {
+ this.passwordPolicy = passwordPolicy;
+ }
+
+ public String getLoginTheme() {
+ return loginTheme;
+ }
+
+ public void setLoginTheme(String theme) {
+ this.loginTheme = theme;
+ }
+
+ public String getAccountTheme() {
+ return accountTheme;
+ }
+
+ public void setAccountTheme(String theme) {
+ this.accountTheme = theme;
+ }
+
+ public String getAdminTheme() {
+ return adminTheme;
+ }
+
+ public void setAdminTheme(String adminTheme) {
+ this.adminTheme = adminTheme;
+ }
+
+ public String getEmailTheme() {
+ return emailTheme;
+ }
+
+ public void setEmailTheme(String emailTheme) {
+ this.emailTheme = emailTheme;
+ }
+
+ public int getNotBefore() {
+ return notBefore;
+ }
+
+ public void setNotBefore(int notBefore) {
+ this.notBefore = notBefore;
+ }
+
+ public boolean isBruteForceProtected() {
+ return bruteForceProtected;
+ }
+
+ public void setBruteForceProtected(boolean bruteForceProtected) {
+ this.bruteForceProtected = bruteForceProtected;
+ }
+
+ public int getMaxFailureWaitSeconds() {
+ return maxFailureWaitSeconds;
+ }
+
+ public void setMaxFailureWaitSeconds(int maxFailureWaitSeconds) {
+ this.maxFailureWaitSeconds = maxFailureWaitSeconds;
+ }
+
+ public int getMinimumQuickLoginWaitSeconds() {
+ return minimumQuickLoginWaitSeconds;
+ }
+
+ public void setMinimumQuickLoginWaitSeconds(int minimumQuickLoginWaitSeconds) {
+ this.minimumQuickLoginWaitSeconds = minimumQuickLoginWaitSeconds;
+ }
+
+ public int getWaitIncrementSeconds() {
+ return waitIncrementSeconds;
+ }
+
+ public void setWaitIncrementSeconds(int waitIncrementSeconds) {
+ this.waitIncrementSeconds = waitIncrementSeconds;
+ }
+
+ public long getQuickLoginCheckMilliSeconds() {
+ return quickLoginCheckMilliSeconds;
+ }
+
+ public void setQuickLoginCheckMilliSeconds(long quickLoginCheckMilliSeconds) {
+ this.quickLoginCheckMilliSeconds = quickLoginCheckMilliSeconds;
+ }
+
+ public int getMaxDeltaTimeSeconds() {
+ return maxDeltaTimeSeconds;
+ }
+
+ public void setMaxDeltaTimeSeconds(int maxDeltaTimeSeconds) {
+ this.maxDeltaTimeSeconds = maxDeltaTimeSeconds;
+ }
+
+ public int getFailureFactor() {
+ return failureFactor;
+ }
+
+ public void setFailureFactor(int failureFactor) {
+ this.failureFactor = failureFactor;
+ }
+
+ public boolean isAuditEnabled() {
+ return auditEnabled;
+ }
+
+ public void setAuditEnabled(boolean auditEnabled) {
+ this.auditEnabled = auditEnabled;
+ }
+
+ public long getAuditExpiration() {
+ return auditExpiration;
+ }
+
+ public void setAuditExpiration(long auditExpiration) {
+ this.auditExpiration = auditExpiration;
+ }
+
+ public Set<String> getAuditListeners() {
+ return auditListeners;
+ }
+
+ public void setAuditListeners(Set<String> auditListeners) {
+ this.auditListeners = auditListeners;
+ }
+
+ public ApplicationEntity getMasterAdminApp() {
+ return masterAdminApp;
+ }
+
+ public void setMasterAdminApp(ApplicationEntity masterAdminApp) {
+ this.masterAdminApp = masterAdminApp;
+ }
+
+}
+
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RequiredCredentialEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RequiredCredentialEntity.java
new file mode 100755
index 0000000..6c14c86
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RequiredCredentialEntity.java
@@ -0,0 +1,64 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+public class RequiredCredentialEntity {
+ @Id
+ @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.realms.jpa.utils.JpaIdGenerator")
+ @GeneratedValue(generator = "keycloak_generator")
+ protected String id;
+
+ protected String type;
+ protected boolean input;
+ protected boolean secret;
+ protected String formLabel;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public boolean isInput() {
+ return input;
+ }
+
+ public void setInput(boolean input) {
+ this.input = input;
+ }
+
+ public boolean isSecret() {
+ return secret;
+ }
+
+ public void setSecret(boolean secret) {
+ this.secret = secret;
+ }
+
+ public String getFormLabel() {
+ return formLabel;
+ }
+
+ public void setFormLabel(String formLabel) {
+ this.formLabel = formLabel;
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RoleEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RoleEntity.java
new file mode 100755
index 0000000..36775ad
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/RoleEntity.java
@@ -0,0 +1,152 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@Table(uniqueConstraints = {
+ @UniqueConstraint(columnNames = { "name", "appRealmConstraint" })
+})
+@NamedQueries({
+ @NamedQuery(name="getAppRoleByName", query="select role from RoleEntity role where role.name = :name and role.application = :application"),
+ @NamedQuery(name="getRealmRoleByName", query="select role from RoleEntity role where role.applicationRole = false and role.name = :name and role.realm = :realm")
+})
+
+public class RoleEntity {
+ @Id
+ @Column(name="id")
+ private String id;
+
+ private String name;
+ private String description;
+
+ // hax! couldn't get constraint to work properly
+ private String realmId;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "realm")
+ private RealmEntity realm;
+
+ @Column(name="applicationRole")
+ private boolean applicationRole;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "application")
+ private ApplicationEntity application;
+
+ // Hack to ensure that either name+application or name+realm are unique. Needed due to MS-SQL as it don't allow multiple NULL values in the column, which is part of constraint
+ private String appRealmConstraint;
+
+ @ManyToMany(fetch = FetchType.LAZY, cascade = {})
+ @JoinTable(name = "CompositeRole", joinColumns = @JoinColumn(name = "composite"), inverseJoinColumns = @JoinColumn(name = "childRole"))
+ private Collection<RoleEntity> compositeRoles = new ArrayList<RoleEntity>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ public void setDescription(String description) {
+ this.description = description;
+ }
+
+ public Collection<RoleEntity> getCompositeRoles() {
+ return compositeRoles;
+ }
+
+ public void setCompositeRoles(Collection<RoleEntity> compositeRoles) {
+ this.compositeRoles = compositeRoles;
+ }
+
+ public boolean isApplicationRole() {
+ return applicationRole;
+ }
+
+ public void setApplicationRole(boolean applicationRole) {
+ this.applicationRole = applicationRole;
+ }
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ this.appRealmConstraint = realm.getId();
+ }
+
+ public ApplicationEntity getApplication() {
+ return application;
+ }
+
+ public void setApplication(ApplicationEntity application) {
+ this.application = application;
+ if (application != null) {
+ this.appRealmConstraint = application.getId();
+ }
+ }
+
+ public String getAppRealmConstraint() {
+ return appRealmConstraint;
+ }
+
+ public void setAppRealmConstraint(String appRealmConstraint) {
+ this.appRealmConstraint = appRealmConstraint;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ RoleEntity that = (RoleEntity) o;
+
+ if (!id.equals(that.id)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ScopeMappingEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ScopeMappingEntity.java
new file mode 100755
index 0000000..b0b65a2
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ScopeMappingEntity.java
@@ -0,0 +1,60 @@
+package org.keycloak.models.realms.jpa.entities;
+
+import org.hibernate.annotations.GenericGenerator;
+
+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 <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @NamedQuery(name="hasScope", query="select m from ScopeMappingEntity m where m.client = :client and m.role = :role"),
+ @NamedQuery(name="clientScopeMappings", query="select m from ScopeMappingEntity m where m.client = :client"),
+ @NamedQuery(name="clientScopeMappingIds", query="select m.role.id from ScopeMappingEntity m where m.client = :client")
+})
+@Entity
+public class ScopeMappingEntity {
+ @Id
+ @GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.realms.jpa.utils.JpaIdGenerator")
+ @GeneratedValue(generator = "keycloak_generator")
+ protected String id;
+ @ManyToOne(fetch= FetchType.LAZY)
+ protected ClientEntity client;
+
+ @ManyToOne(fetch= FetchType.LAZY)
+ @JoinColumn(name="roleId")
+ protected RoleEntity role;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public ClientEntity getClient() {
+ return client;
+ }
+
+ public void setClient(ClientEntity client) {
+ this.client = client;
+ }
+
+ public RoleEntity getRole() {
+ return role;
+ }
+
+ public void setRole(RoleEntity role) {
+ this.role = role;
+ }
+
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaKeycloakTransaction.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaKeycloakTransaction.java
new file mode 100755
index 0000000..fd41714
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaKeycloakTransaction.java
@@ -0,0 +1,53 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.KeycloakTransaction;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaKeycloakTransaction implements KeycloakTransaction {
+
+ protected EntityManager em;
+
+ public JpaKeycloakTransaction(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public void begin() {
+ em.getTransaction().begin();
+ }
+
+ @Override
+ public void commit() {
+ try {
+ em.getTransaction().commit();
+ } catch (PersistenceException e) {
+ throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
+ }
+ }
+
+ @Override
+ public void rollback() {
+ em.getTransaction().rollback();
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ em.getTransaction().setRollbackOnly();
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return em.getTransaction().getRollbackOnly();
+ }
+
+ @Override
+ public boolean isActive() {
+ return em.getTransaction().isActive();
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProvider.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProvider.java
new file mode 100755
index 0000000..9f8f043
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProvider.java
@@ -0,0 +1,135 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.realms.OAuthClient;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.realms.jpa.entities.ApplicationEntity;
+import org.keycloak.models.realms.jpa.entities.OAuthClientEntity;
+import org.keycloak.models.realms.jpa.entities.RealmEntity;
+import org.keycloak.models.realms.jpa.entities.RoleEntity;
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaRealmProvider implements RealmProvider {
+
+ protected final EntityManager em;
+
+ public JpaRealmProvider(EntityManager em) {
+ this.em = PersistenceExceptionConverter.create(em);
+ }
+
+ @Override
+ public KeycloakTransaction getTransaction() {
+ return new JpaKeycloakTransaction(em);
+ }
+
+ @Override
+ public Realm createRealm(String name) {
+ return createRealm(KeycloakModelUtils.generateId(), name);
+ }
+
+ @Override
+ public Realm createRealm(String id, String name) {
+ RealmEntity realm = new RealmEntity();
+ realm.setName(name);
+ realm.setId(id);
+ em.persist(realm);
+ em.flush();
+ return new RealmAdapter(this, em, realm);
+ }
+
+ @Override
+ public Realm getRealm(String id) {
+ RealmEntity realm = em.find(RealmEntity.class, id);
+ if (realm == null) return null;
+ return new RealmAdapter(this, em, realm);
+ }
+
+ @Override
+ public List<Realm> getRealms() {
+ TypedQuery<RealmEntity> query = em.createNamedQuery("getAllRealms", RealmEntity.class);
+ List<RealmEntity> entities = query.getResultList();
+ List<Realm> realms = new ArrayList<Realm>();
+ for (RealmEntity entity : entities) {
+ realms.add(new RealmAdapter(this, em, entity));
+ }
+ return realms;
+ }
+
+ @Override
+ public Realm getRealmByName(String name) {
+ TypedQuery<RealmEntity> query = em.createNamedQuery("getRealmByName", RealmEntity.class);
+ query.setParameter("name", name);
+ List<RealmEntity> entities = query.getResultList();
+ if (entities.size() == 0) return null;
+ if (entities.size() > 1) throw new IllegalStateException("Should not be more than one realm with same name");
+ RealmEntity realm = query.getResultList().get(0);
+ if (realm == null) return null;
+ return new RealmAdapter(this, em, realm);
+ }
+
+ @Override
+ public boolean removeRealm(String id) {
+ RealmEntity realm = em.find(RealmEntity.class, id);
+ if (realm == null) {
+ return false;
+ }
+
+ RealmAdapter adapter = new RealmAdapter(this, em, realm);
+ for (Application a : adapter.getApplications()) {
+ adapter.removeApplication(a);
+ }
+
+ for (OAuthClient oauth : adapter.getOAuthClients()) {
+ adapter.removeOAuthClient(oauth);
+ }
+
+ em.remove(realm);
+
+ return true;
+ }
+
+ @Override
+ public void close() {
+ if (em.getTransaction().isActive()) em.getTransaction().rollback();
+ if (em.isOpen()) em.close();
+ }
+
+ @Override
+ public Role getRoleById(String id, String realm) {
+ RoleEntity entity = em.find(RoleEntity.class, id);
+ if (entity == null) return null;
+ if (!realm.equals(entity.getRealmId())) return null;
+ return new RoleAdapter(this, em, entity);
+ }
+
+ @Override
+ public Application getApplicationById(String id, String realm) {
+ ApplicationEntity app = em.find(ApplicationEntity.class, id);
+
+ // Check if application belongs to this realm
+ if (app == null || !realm.equals(app.getRealm().getId())) return null;
+ return new ApplicationAdapter(this, em, app);
+ }
+
+ @Override
+ public OAuthClient getOAuthClientById(String id, String realm) {
+ OAuthClientEntity client = em.find(OAuthClientEntity.class, id);
+
+ // Check if client belongs to this realm
+ if (client == null || !realm.equals(client.getRealm().getId())) return null;
+ return new OAuthClientAdapter(this, client, em);
+ }
+
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProviderFactory.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProviderFactory.java
new file mode 100755
index 0000000..e181b8e
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaRealmProviderFactory.java
@@ -0,0 +1,41 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.realms.RealmProviderFactory;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.util.JpaUtils;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaRealmProviderFactory implements RealmProviderFactory {
+
+ protected EntityManagerFactory emf;
+
+ @Override
+ public void init(Config.Scope config) {
+ String persistenceUnit = config.get("persistenceUnit", "jpa-keycloak-identity-store");
+ emf = Persistence.createEntityManagerFactory(persistenceUnit, JpaUtils.getHibernateProperties());
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+
+ @Override
+ public RealmProvider create(KeycloakSession session) {
+ return new JpaRealmProvider(emf.createEntityManager());
+ }
+
+ @Override
+ public void close() {
+ emf.close();
+ }
+
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/OAuthClientAdapter.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/OAuthClientAdapter.java
new file mode 100755
index 0000000..39bf489
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/OAuthClientAdapter.java
@@ -0,0 +1,53 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.realms.OAuthClient;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.jpa.entities.OAuthClientEntity;
+
+import javax.persistence.EntityManager;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class OAuthClientAdapter extends ClientAdapter implements OAuthClient {
+
+ protected final OAuthClientEntity oAuthClientEntity;
+
+ public OAuthClientAdapter(RealmProvider provider, OAuthClientEntity entity, EntityManager em) {
+ super(provider, entity, em);
+ oAuthClientEntity = entity;
+ }
+
+ @Override
+ public void setClientId(String id) {
+ entity.setName(id);
+
+ }
+
+ @Override
+ public boolean isDirectGrantsOnly() {
+ return oAuthClientEntity.isDirectGrantsOnly();
+ }
+
+ @Override
+ public void setDirectGrantsOnly(boolean flag) {
+ oAuthClientEntity.setDirectGrantsOnly(flag);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof OAuthClient)) return false;
+
+ OAuthClient that = (OAuthClient) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/PersistenceExceptionConverter.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/PersistenceExceptionConverter.java
new file mode 100644
index 0000000..f479939
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/PersistenceExceptionConverter.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.realms.jpa;
+
+import org.hibernate.exception.ConstraintViolationException;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+
+import javax.persistence.EntityExistsException;
+import javax.persistence.EntityManager;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PersistenceExceptionConverter implements InvocationHandler {
+
+ private EntityManager em;
+
+ public static EntityManager create(EntityManager em) {
+ return (EntityManager) Proxy.newProxyInstance(EntityManager.class.getClassLoader(), new Class[]{EntityManager.class}, new PersistenceExceptionConverter(em));
+ }
+
+ private PersistenceExceptionConverter(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ return method.invoke(em, args);
+ } catch (InvocationTargetException e) {
+ throw convert(e.getCause());
+ }
+ }
+
+ public static ModelException convert(Throwable t) {
+ if (t.getCause() != null && t.getCause() instanceof ConstraintViolationException) {
+ throw new ModelDuplicateException(t);
+ } if (t instanceof EntityExistsException) {
+ throw new ModelDuplicateException(t);
+ } else {
+ throw new ModelException(t);
+ }
+ }
+
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/RealmAdapter.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/RealmAdapter.java
new file mode 100755
index 0000000..578fa65
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/RealmAdapter.java
@@ -0,0 +1,853 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.realms.Application;
+import org.keycloak.models.realms.Client;
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.PasswordPolicy;
+import org.keycloak.models.realms.OAuthClient;
+import org.keycloak.models.realms.Realm;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.realms.jpa.entities.ApplicationEntity;
+import org.keycloak.models.realms.jpa.entities.AuthenticationProviderEntity;
+import org.keycloak.models.realms.jpa.entities.RealmEntity;
+import org.keycloak.models.realms.jpa.entities.RequiredCredentialEntity;
+import org.keycloak.models.realms.jpa.entities.RoleEntity;
+import org.keycloak.models.realms.jpa.entities.ScopeMappingEntity;
+import org.keycloak.models.realms.jpa.entities.OAuthClientEntity;
+import org.keycloak.models.RequiredCredentialModel;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RealmAdapter implements Realm {
+ protected RealmEntity realm;
+ protected RealmProvider provider;
+ protected EntityManager em;
+ protected volatile transient PublicKey publicKey;
+ protected volatile transient PrivateKey privateKey;
+ protected PasswordPolicy passwordPolicy;
+
+ public RealmAdapter(RealmProvider provider, EntityManager em, RealmEntity realm) {
+ this.provider = provider;
+ this.em = em;
+ this.realm = realm;
+ }
+
+ public RealmEntity getEntity() {
+ return realm;
+ }
+
+ @Override
+ public String getId() {
+ return realm.getId();
+ }
+
+ @Override
+ public String getName() {
+ return realm.getName();
+ }
+
+ @Override
+ public void setName(String name) {
+ realm.setName(name);
+ em.flush();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return realm.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ realm.setEnabled(enabled);
+ em.flush();
+ }
+
+ @Override
+ public boolean isSslNotRequired() {
+ return realm.isSslNotRequired();
+ }
+
+ @Override
+ public void setSslNotRequired(boolean sslNotRequired) {
+ realm.setSslNotRequired(sslNotRequired);
+ em.flush();
+ }
+
+ @Override
+ public boolean isPasswordCredentialGrantAllowed() {
+ return realm.isPasswordCredentialGrantAllowed();
+ }
+
+ @Override
+ public void setPasswordCredentialGrantAllowed(boolean passwordCredentialGrantAllowed) {
+ realm.setPasswordCredentialGrantAllowed(passwordCredentialGrantAllowed);
+ em.flush();
+ }
+
+ @Override
+ public boolean isRegistrationAllowed() {
+ return realm.isRegistrationAllowed();
+ }
+
+ @Override
+ public void setRegistrationAllowed(boolean registrationAllowed) {
+ realm.setRegistrationAllowed(registrationAllowed);
+ em.flush();
+ }
+
+ @Override
+ public boolean isRememberMe() {
+ return realm.isRememberMe();
+ }
+
+ @Override
+ public void setRememberMe(boolean rememberMe) {
+ realm.setRememberMe(rememberMe);
+ em.flush();
+ }
+
+ @Override
+ public boolean isBruteForceProtected() {
+ return realm.isBruteForceProtected();
+ }
+
+ @Override
+ public void setBruteForceProtected(boolean value) {
+ realm.setBruteForceProtected(value);
+ }
+
+ @Override
+ public int getMaxFailureWaitSeconds() {
+ return realm.getMaxFailureWaitSeconds();
+ }
+
+ @Override
+ public void setMaxFailureWaitSeconds(int val) {
+ realm.setMaxFailureWaitSeconds(val);
+ }
+
+ @Override
+ public int getWaitIncrementSeconds() {
+ return realm.getWaitIncrementSeconds();
+ }
+
+ @Override
+ public void setWaitIncrementSeconds(int val) {
+ realm.setWaitIncrementSeconds(val);
+ }
+
+ @Override
+ public long getQuickLoginCheckMilliSeconds() {
+ return realm.getQuickLoginCheckMilliSeconds();
+ }
+
+ @Override
+ public void setQuickLoginCheckMilliSeconds(long val) {
+ realm.setQuickLoginCheckMilliSeconds(val);
+ }
+
+ @Override
+ public int getMinimumQuickLoginWaitSeconds() {
+ return realm.getMinimumQuickLoginWaitSeconds();
+ }
+
+ @Override
+ public void setMinimumQuickLoginWaitSeconds(int val) {
+ realm.setMinimumQuickLoginWaitSeconds(val);
+ }
+
+ @Override
+ public int getMaxDeltaTimeSeconds() {
+ return realm.getMaxDeltaTimeSeconds();
+ }
+
+ @Override
+ public void setMaxDeltaTimeSeconds(int val) {
+ realm.setMaxDeltaTimeSeconds(val);
+ }
+
+ @Override
+ public int getFailureFactor() {
+ return realm.getFailureFactor();
+ }
+
+ @Override
+ public void setFailureFactor(int failureFactor) {
+ realm.setFailureFactor(failureFactor);
+ }
+
+ @Override
+ public boolean isVerifyEmail() {
+ return realm.isVerifyEmail();
+ }
+
+ @Override
+ public void setVerifyEmail(boolean verifyEmail) {
+ realm.setVerifyEmail(verifyEmail);
+ em.flush();
+ }
+
+ @Override
+ public boolean isResetPasswordAllowed() {
+ return realm.isResetPasswordAllowed();
+ }
+
+ @Override
+ public void setResetPasswordAllowed(boolean resetPasswordAllowed) {
+ realm.setResetPasswordAllowed(resetPasswordAllowed);
+ em.flush();
+ }
+
+ @Override
+ public int getNotBefore() {
+ return realm.getNotBefore();
+ }
+
+ @Override
+ public void setNotBefore(int notBefore) {
+ realm.setNotBefore(notBefore);
+ }
+
+ @Override
+ public int getAccessTokenLifespan() {
+ return realm.getAccessTokenLifespan();
+ }
+
+ @Override
+ public void setAccessTokenLifespan(int tokenLifespan) {
+ realm.setAccessTokenLifespan(tokenLifespan);
+ em.flush();
+ }
+
+ @Override
+ public int getSsoSessionIdleTimeout() {
+ return realm.getSsoSessionIdleTimeout();
+ }
+
+ @Override
+ public void setSsoSessionIdleTimeout(int seconds) {
+ realm.setSsoSessionIdleTimeout(seconds);
+ }
+
+ @Override
+ public int getSsoSessionMaxLifespan() {
+ return realm.getSsoSessionMaxLifespan();
+ }
+
+ @Override
+ public void setSsoSessionMaxLifespan(int seconds) {
+ realm.setSsoSessionMaxLifespan(seconds);
+ }
+
+ @Override
+ public int getAccessCodeLifespan() {
+ return realm.getAccessCodeLifespan();
+ }
+
+ @Override
+ public void setAccessCodeLifespan(int accessCodeLifespan) {
+ realm.setAccessCodeLifespan(accessCodeLifespan);
+ em.flush();
+ }
+
+ @Override
+ public int getAccessCodeLifespanUserAction() {
+ return realm.getAccessCodeLifespanUserAction();
+ }
+
+ @Override
+ public void setAccessCodeLifespanUserAction(int accessCodeLifespanUserAction) {
+ realm.setAccessCodeLifespanUserAction(accessCodeLifespanUserAction);
+ em.flush();
+ }
+
+ @Override
+ public String getPublicKeyPem() {
+ return realm.getPublicKeyPem();
+ }
+
+ @Override
+ public void setPublicKeyPem(String publicKeyPem) {
+ realm.setPublicKeyPem(publicKeyPem);
+ em.flush();
+ }
+
+ @Override
+ public String getPrivateKeyPem() {
+ return realm.getPrivateKeyPem();
+ }
+
+ @Override
+ public void setPrivateKeyPem(String privateKeyPem) {
+ realm.setPrivateKeyPem(privateKeyPem);
+ em.flush();
+ }
+
+ @Override
+ public PublicKey getPublicKey() {
+ if (publicKey != null) return publicKey;
+ publicKey = KeycloakModelUtils.getPublicKey(getPublicKeyPem());
+ return publicKey;
+ }
+
+ @Override
+ public void setPublicKey(PublicKey publicKey) {
+ this.publicKey = publicKey;
+ String publicKeyPem = KeycloakModelUtils.getPemFromKey(publicKey);
+ setPublicKeyPem(publicKeyPem);
+ }
+
+ @Override
+ public PrivateKey getPrivateKey() {
+ if (privateKey != null) return privateKey;
+ privateKey = KeycloakModelUtils.getPrivateKey(getPrivateKeyPem());
+ return privateKey;
+ }
+
+ @Override
+ public void setPrivateKey(PrivateKey privateKey) {
+ this.privateKey = privateKey;
+ String privateKeyPem = KeycloakModelUtils.getPemFromKey(privateKey);
+ setPrivateKeyPem(privateKeyPem);
+ }
+
+ protected RequiredCredentialModel initRequiredCredentialModel(String type) {
+ RequiredCredentialModel model = RequiredCredentialModel.BUILT_IN.get(type);
+ if (model == null) {
+ throw new RuntimeException("Unknown credential type " + type);
+ }
+ return model;
+ }
+
+ @Override
+ public void addRequiredCredential(String type) {
+ RequiredCredentialModel model = initRequiredCredentialModel(type);
+ addRequiredCredential(model);
+ em.flush();
+ }
+
+ public void addRequiredCredential(RequiredCredentialModel model) {
+ RequiredCredentialEntity entity = new RequiredCredentialEntity();
+ entity.setInput(model.isInput());
+ entity.setSecret(model.isSecret());
+ entity.setType(model.getType());
+ entity.setFormLabel(model.getFormLabel());
+ em.persist(entity);
+ realm.getRequiredCredentials().add(entity);
+ em.flush();
+ }
+
+ @Override
+ public void updateRequiredCredentials(Set<String> creds) {
+ Collection<RequiredCredentialEntity> relationships = realm.getRequiredCredentials();
+ if (relationships == null) relationships = new ArrayList<RequiredCredentialEntity>();
+
+ Set<String> already = new HashSet<String>();
+ List<RequiredCredentialEntity> remove = new ArrayList<RequiredCredentialEntity>();
+ for (RequiredCredentialEntity rel : relationships) {
+ if (!creds.contains(rel.getType())) {
+ remove.add(rel);
+ } else {
+ already.add(rel.getType());
+ }
+ }
+ for (RequiredCredentialEntity entity : remove) {
+ relationships.remove(entity);
+ em.remove(entity);
+ }
+ for (String cred : creds) {
+ if (!already.contains(cred)) {
+ addRequiredCredential(cred);
+ }
+ }
+ em.flush();
+ }
+
+
+ @Override
+ public List<RequiredCredentialModel> getRequiredCredentials() {
+ List<RequiredCredentialModel> requiredCredentialModels = new ArrayList<RequiredCredentialModel>();
+ Collection<RequiredCredentialEntity> entities = realm.getRequiredCredentials();
+ if (entities == null) return requiredCredentialModels;
+ for (RequiredCredentialEntity entity : entities) {
+ RequiredCredentialModel model = new RequiredCredentialModel();
+ model.setFormLabel(entity.getFormLabel());
+ model.setType(entity.getType());
+ model.setSecret(entity.isSecret());
+ model.setInput(entity.isInput());
+ requiredCredentialModels.add(model);
+ }
+ return requiredCredentialModels; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List<String> getDefaultRoles() {
+ Collection<RoleEntity> entities = realm.getDefaultRoles();
+ List<String> roles = new ArrayList<String>();
+ if (entities == null) return roles;
+ for (RoleEntity entity : entities) {
+ roles.add(entity.getName());
+ }
+ return roles;
+ }
+
+ @Override
+ public void addDefaultRole(String name) {
+ Role role = getRole(name);
+ Collection<RoleEntity> entities = realm.getDefaultRoles();
+ for (RoleEntity entity : entities) {
+ if (entity.getId().equals(role.getId())) {
+ return;
+ }
+ }
+ realm.getDefaultRoles().add(((RoleAdapter) role).getRole());
+ em.flush();
+ }
+
+ public static boolean contains(String str, String[] array) {
+ for (String s : array) {
+ if (str.equals(s)) return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void updateDefaultRoles(String[] defaultRoles) {
+ Collection<RoleEntity> entities = realm.getDefaultRoles();
+ Set<String> already = new HashSet<String>();
+ List<RoleEntity> remove = new ArrayList<RoleEntity>();
+ for (RoleEntity rel : entities) {
+ if (!contains(rel.getName(), defaultRoles)) {
+ remove.add(rel);
+ } else {
+ already.add(rel.getName());
+ }
+ }
+ for (RoleEntity entity : remove) {
+ entities.remove(entity);
+ }
+ em.flush();
+ for (String roleName : defaultRoles) {
+ if (!already.contains(roleName)) {
+ addDefaultRole(roleName);
+ }
+ }
+ em.flush();
+ }
+
+ @Override
+ public Client findClient(String clientId) {
+ Client model = getApplicationByName(clientId);
+ if (model != null) return model;
+ return getOAuthClient(clientId);
+ }
+
+ @Override
+ public Map<String, Application> getApplicationNameMap() {
+ Map<String, Application> map = new HashMap<String, Application>();
+ for (Application app : getApplications()) {
+ map.put(app.getName(), app);
+ }
+ return map; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List<Application> getApplications() {
+ List<Application> list = new ArrayList<Application>();
+ if (realm.getApplications() == null) return list;
+ for (ApplicationEntity entity : realm.getApplications()) {
+ list.add(new ApplicationAdapter(provider, em, entity));
+ }
+ return list;
+ }
+
+ @Override
+ public Application addApplication(String id, String name) {
+ ApplicationEntity applicationData = new ApplicationEntity();
+ applicationData.setId(id);
+ applicationData.setName(name);
+ applicationData.setEnabled(true);
+ applicationData.setRealm(realm);
+ realm.getApplications().add(applicationData);
+ em.persist(applicationData);
+ em.flush();
+ Application resource = new ApplicationAdapter(provider, em, applicationData);
+ em.flush();
+ return resource;
+ }
+
+ @Override
+ public boolean removeApplication(Application application) {
+ for (Role role : application.getRoles()) {
+ application.removeRole(role);
+ }
+
+ ApplicationEntity applicationEntity = null;
+ Iterator<ApplicationEntity> it = realm.getApplications().iterator();
+ while (it.hasNext()) {
+ ApplicationEntity ae = it.next();
+ if (ae.getId().equals(application.getId())) {
+ applicationEntity = ae;
+ it.remove();
+ break;
+ }
+ }
+ for (ApplicationEntity a : realm.getApplications()) {
+ if (a.getId().equals(application.getId())) {
+ applicationEntity = a;
+ }
+ }
+ if (application == null) {
+ return false;
+ }
+ em.remove(applicationEntity);
+ em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where client = :client").setParameter("client", applicationEntity).executeUpdate();
+
+ return true;
+ }
+
+ @Override
+ public Application getApplicationByName(String name) {
+ return getApplicationNameMap().get(name);
+ }
+
+ @Override
+ public boolean isSocial() {
+ return realm.isSocial();
+ }
+
+ @Override
+ public void setSocial(boolean social) {
+ realm.setSocial(social);
+ em.flush();
+ }
+
+ @Override
+ public boolean isUpdateProfileOnInitialSocialLogin() {
+ return realm.isUpdateProfileOnInitialSocialLogin();
+ }
+
+ @Override
+ public void setUpdateProfileOnInitialSocialLogin(boolean updateProfileOnInitialSocialLogin) {
+ realm.setUpdateProfileOnInitialSocialLogin(updateProfileOnInitialSocialLogin);
+ em.flush();
+ }
+
+ @Override
+ public OAuthClient addOAuthClient(String id, String name) {
+ OAuthClientEntity data = new OAuthClientEntity();
+ data.setId(id);
+ data.setEnabled(true);
+ data.setName(name);
+ data.setRealm(realm);
+ em.persist(data);
+ em.flush();
+ return new OAuthClientAdapter(provider, data, em);
+ }
+
+ @Override
+ public boolean removeOAuthClient(OAuthClient client) {
+ em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where client = :client").setParameter("client", client).executeUpdate();
+ em.remove(client);
+ return true;
+ }
+
+
+ @Override
+ public OAuthClient getOAuthClient(String name) {
+ TypedQuery<OAuthClientEntity> query = em.createNamedQuery("findOAuthClientByName", OAuthClientEntity.class);
+ query.setParameter("name", name);
+ query.setParameter("realm", realm);
+ List<OAuthClientEntity> entities = query.getResultList();
+ if (entities.size() == 0) return null;
+ return new OAuthClientAdapter(provider, entities.get(0), em);
+ }
+
+ @Override
+ public List<OAuthClient> getOAuthClients() {
+ TypedQuery<OAuthClientEntity> query = em.createNamedQuery("findOAuthClientByRealm", OAuthClientEntity.class);
+ query.setParameter("realm", realm);
+ List<OAuthClientEntity> entities = query.getResultList();
+ List<OAuthClient> list = new ArrayList<OAuthClient>();
+ for (OAuthClientEntity entity : entities) list.add(new OAuthClientAdapter(provider, entity, em));
+ return list;
+ }
+
+ @Override
+ public Map<String, String> getSmtpConfig() {
+ return realm.getSmtpConfig();
+ }
+
+ @Override
+ public void setSmtpConfig(Map<String, String> smtpConfig) {
+ realm.setSmtpConfig(smtpConfig);
+ em.flush();
+ }
+
+ @Override
+ public Map<String, String> getSocialConfig() {
+ return realm.getSocialConfig();
+ }
+
+ @Override
+ public void setSocialConfig(Map<String, String> socialConfig) {
+ realm.setSocialConfig(socialConfig);
+ em.flush();
+ }
+
+ @Override
+ public Map<String, String> getLdapServerConfig() {
+ return realm.getLdapServerConfig();
+ }
+
+ @Override
+ public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
+ realm.setLdapServerConfig(ldapServerConfig);
+ em.flush();
+ }
+
+ @Override
+ public List<AuthenticationProviderModel> getAuthenticationProviders() {
+ List<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
+ List<AuthenticationProviderEntity> copy = new ArrayList<AuthenticationProviderEntity>();
+ for (AuthenticationProviderEntity entity : entities) {
+ copy.add(entity);
+
+ }
+ Collections.sort(copy, new Comparator<AuthenticationProviderEntity>() {
+
+ @Override
+ public int compare(AuthenticationProviderEntity o1, AuthenticationProviderEntity o2) {
+ return o1.getPriority() - o2.getPriority();
+ }
+
+ });
+ List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
+ for (AuthenticationProviderEntity entity : copy) {
+ result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
+ }
+
+ return result;
+ }
+
+ @Override
+ public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
+ List<AuthenticationProviderEntity> newEntities = new ArrayList<AuthenticationProviderEntity>();
+ int counter = 1;
+ for (AuthenticationProviderModel model : authenticationProviders) {
+ AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
+ entity.setProviderName(model.getProviderName());
+ entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
+ entity.setConfig(model.getConfig());
+ entity.setPriority(counter++);
+ newEntities.add(entity);
+ }
+
+ // Remove all existing first
+ Collection<AuthenticationProviderEntity> existing = realm.getAuthenticationProviders();
+ Collection<AuthenticationProviderEntity> copy = new ArrayList<AuthenticationProviderEntity>(existing);
+ for (AuthenticationProviderEntity apToRemove : copy) {
+ existing.remove(apToRemove);
+ em.remove(apToRemove);
+ }
+
+ // Now create all new providers
+ for (AuthenticationProviderEntity apToAdd : newEntities) {
+ existing.add(apToAdd);
+ em.persist(apToAdd);
+ }
+
+ em.flush();
+ }
+
+ @Override
+ public Role getRole(String name) {
+ TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class);
+ query.setParameter("name", name);
+ query.setParameter("realm", realm);
+ List<RoleEntity> roles = query.getResultList();
+ if (roles.size() == 0) return null;
+ return new RoleAdapter(provider, em, roles.get(0));
+ }
+
+ @Override
+ public Role addRole(String id, String name) {
+ RoleEntity entity = new RoleEntity();
+ entity.setId(id);
+ entity.setName(name);
+ entity.setRealm(realm);
+ entity.setRealmId(realm.getId());
+ em.persist(entity);
+ realm.getRoles().add(entity);
+ em.flush();
+ return new RoleAdapter(provider, em, entity);
+ }
+
+ @Override
+ public boolean removeRole(Role role) {
+ if (role == null) {
+ return false;
+ }
+ if (!role.getContainer().equals(this)) return false;
+
+ RoleEntity roleEntity = ((RoleAdapter) role).getRole();
+ realm.getRoles().remove(role);
+ realm.getDefaultRoles().remove(role);
+
+ em.createNativeQuery("delete from CompositeRole where childRole = :role").setParameter("role", roleEntity).executeUpdate();
+ em.createQuery("delete from " + ScopeMappingEntity.class.getSimpleName() + " where role = :role").setParameter("role", roleEntity).executeUpdate();
+
+ em.remove(roleEntity);
+
+ return true;
+ }
+
+ @Override
+ public Set<Role> getRoles() {
+ Set<Role> list = new HashSet<Role>();
+ Collection<RoleEntity> roles = realm.getRoles();
+ if (roles == null) return list;
+ for (RoleEntity entity : roles) {
+ list.add(new RoleAdapter(provider, em, entity));
+ }
+ return list;
+ }
+
+ @Override
+ public PasswordPolicy getPasswordPolicy() {
+ if (passwordPolicy == null) {
+ passwordPolicy = new PasswordPolicy(realm.getPasswordPolicy());
+ }
+ return passwordPolicy;
+ }
+
+ @Override
+ public void setPasswordPolicy(PasswordPolicy policy) {
+ this.passwordPolicy = policy;
+ realm.setPasswordPolicy(policy.toString());
+ em.flush();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof Realm)) return false;
+
+ Realm that = (Realm) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+ @Override
+ public String getLoginTheme() {
+ return realm.getLoginTheme();
+ }
+
+ @Override
+ public void setLoginTheme(String name) {
+ realm.setLoginTheme(name);
+ em.flush();
+ }
+
+ @Override
+ public String getAccountTheme() {
+ return realm.getAccountTheme();
+ }
+
+ @Override
+ public void setAccountTheme(String name) {
+ realm.setAccountTheme(name);
+ em.flush();
+ }
+
+ @Override
+ public String getAdminTheme() {
+ return realm.getAdminTheme();
+ }
+
+ @Override
+ public void setAdminTheme(String name) {
+ realm.setAdminTheme(name);
+ em.flush();
+ }
+
+ @Override
+ public String getEmailTheme() {
+ return realm.getEmailTheme();
+ }
+
+ @Override
+ public void setEmailTheme(String name) {
+ realm.setEmailTheme(name);
+ em.flush();
+ }
+
+ @Override
+ public boolean isAuditEnabled() {
+ return realm.isAuditEnabled();
+ }
+
+ @Override
+ public void setAuditEnabled(boolean enabled) {
+ realm.setAuditEnabled(enabled);
+ em.flush();
+ }
+
+ @Override
+ public long getAuditExpiration() {
+ return realm.getAuditExpiration();
+ }
+
+ @Override
+ public void setAuditExpiration(long expiration) {
+ realm.setAuditExpiration(expiration);
+ em.flush();
+ }
+
+ @Override
+ public Set<String> getAuditListeners() {
+ return realm.getAuditListeners();
+ }
+
+ @Override
+ public void setAuditListeners(Set<String> listeners) {
+ realm.setAuditListeners(listeners);
+ em.flush();
+ }
+
+ @Override
+ public Application getMasterAdminApp() {
+ return new ApplicationAdapter(provider, em, realm.getMasterAdminApp());
+ }
+
+ @Override
+ public void setMasterAdminApp(Application app) {
+ realm.setMasterAdminApp(((ApplicationAdapter) app).getJpaEntity());
+ em.flush();
+ }
+
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/RoleAdapter.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/RoleAdapter.java
new file mode 100755
index 0000000..0063794
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/RoleAdapter.java
@@ -0,0 +1,117 @@
+package org.keycloak.models.realms.jpa;
+
+import org.keycloak.models.realms.RealmProvider;
+import org.keycloak.models.realms.Role;
+import org.keycloak.models.realms.RoleContainer;
+import org.keycloak.models.realms.jpa.entities.RoleEntity;
+
+import javax.persistence.EntityManager;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class RoleAdapter implements Role {
+ protected RoleEntity role;
+ protected RealmProvider provider;
+ protected EntityManager em;
+
+ public RoleAdapter(RealmProvider provider, EntityManager em, RoleEntity role) {
+ this.provider = provider;
+ this.em = em;
+ this.role = role;
+ }
+
+ public RoleEntity getRole() {
+ return role;
+ }
+
+ public void setRole(RoleEntity role) {
+ this.role = role;
+ }
+
+ @Override
+ public String getName() {
+ return role.getName();
+ }
+
+ @Override
+ public String getDescription() {
+ return role.getDescription();
+ }
+
+ @Override
+ public void setDescription(String description) {
+ role.setDescription(description);
+ }
+
+ @Override
+ public String getId() {
+ return role.getId();
+ }
+
+ @Override
+ public void setName(String name) {
+ role.setName(name);
+ }
+
+ @Override
+ public boolean isComposite() {
+ return getComposites().size() > 0;
+ }
+
+ @Override
+ public void addCompositeRole(Role role) {
+ RoleEntity entity = ((RoleAdapter)role).getRole();
+ for (RoleEntity composite : getRole().getCompositeRoles()) {
+ if (composite.equals(entity)) return;
+ }
+ getRole().getCompositeRoles().add(entity);
+ em.flush();
+ }
+
+ @Override
+ public void removeCompositeRole(Role role) {
+ RoleEntity entity = ((RoleAdapter)role).getRole();
+ Iterator<RoleEntity> it = getRole().getCompositeRoles().iterator();
+ while (it.hasNext()) {
+ if (it.next().equals(entity)) it.remove();
+ }
+ }
+
+ @Override
+ public Set<Role> getComposites() {
+ Set<Role> set = new HashSet<Role>();
+
+ for (RoleEntity composite : getRole().getCompositeRoles()) {
+ set.add(new RoleAdapter(provider, em, composite));
+ }
+ return set;
+ }
+
+ @Override
+ public RoleContainer getContainer() {
+ if (role.isApplicationRole()) {
+ return provider.getApplicationById(role.getApplication().getId(), role.getApplication().getRealm().getId());
+ } else {
+ return provider.getRealm(role.getRealm().getId());
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof Role)) return false;
+
+ Role that = (Role) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+}
diff --git a/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/utils/JpaIdGenerator.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/utils/JpaIdGenerator.java
new file mode 100644
index 0000000..5ff77d5
--- /dev/null
+++ b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/utils/JpaIdGenerator.java
@@ -0,0 +1,19 @@
+package org.keycloak.models.realms.jpa.utils;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SessionImplementor;
+import org.hibernate.id.IdentifierGenerator;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JpaIdGenerator implements IdentifierGenerator {
+
+ @Override
+ public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
+ return KeycloakModelUtils.generateId();
+ }
+}
diff --git a/model/realms-jpa/src/main/resources/META-INF/services/org.keycloak.models.realms.RealmProviderFactory b/model/realms-jpa/src/main/resources/META-INF/services/org.keycloak.models.realms.RealmProviderFactory
new file mode 100644
index 0000000..81ec90a
--- /dev/null
+++ b/model/realms-jpa/src/main/resources/META-INF/services/org.keycloak.models.realms.RealmProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.realms.jpa.JpaRealmProviderFactory
\ No newline at end of file
diff --git a/model/realms-jpa/src/test/resources/META-INF/persistence.xml b/model/realms-jpa/src/test/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..a150d05
--- /dev/null
+++ b/model/realms-jpa/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,70 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+ version="1.0">
+ <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+ <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+ <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
+ <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
+ <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
+ <class>org.keycloak.models.jpa.entities.RealmEntity</class>
+ <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
+ <class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
+ <class>org.keycloak.models.jpa.entities.RoleEntity</class>
+ <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
+ <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
+ <class>org.keycloak.models.jpa.entities.UserEntity</class>
+ <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
+ <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
+ <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
+ <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
+ <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
+
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+ <properties>
+ <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+ <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+ <property name="hibernate.connection.username" value="sa"/>
+ <property name="hibernate.connection.password" value=""/>
+ <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+ <property name="hibernate.show_sql" value="false" />
+ <property name="hibernate.format_sql" value="true" />
+ </properties>
+ </persistence-unit>
+
+ <!--
+ <persistence-unit name="picketlink-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+ <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+ <class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
+ <class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
+ <class>org.keycloak.models.picketlink.mappings.RealmEntity</class>
+ <class>org.keycloak.models.picketlink.mappings.ApplicationEntity</class>
+
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+ <properties>
+ <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+ <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+ <property name="hibernate.connection.username" value="sa"/>
+ <property name="hibernate.connection.password" value=""/>
+ <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+ <property name="hibernate.show_sql" value="false" />
+ <property name="hibernate.format_sql" value="true" />
+ </properties>
+ </persistence-unit>
+ -->
+</persistence>
model/sessions-jpa/pom.xml 45(+45 -0)
diff --git a/model/sessions-jpa/pom.xml b/model/sessions-jpa/pom.xml
new file mode 100755
index 0000000..4eb7265
--- /dev/null
+++ b/model/sessions-jpa/pom.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-model-sessions-jpa</artifactId>
+ <name>Keycloak Model Sessions JPA</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.0-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <version>${hibernate.entitymanager.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionAssociationEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionAssociationEntity.java
new file mode 100755
index 0000000..7a018eb
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionAssociationEntity.java
@@ -0,0 +1,84 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@Table(name = "ClientUserSessionAscEntity")
+@NamedQueries({
+ @NamedQuery(name = "getAllClientUserSessions", query = "select s from ClientUserSessionAssociationEntity s"),
+ @NamedQuery(name = "getClientUserSessionBySession", query = "select s from ClientUserSessionAssociationEntity s where s.session = :session"),
+ @NamedQuery(name = "getClientUserSessionByClient", query = "select s from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
+ @NamedQuery(name = "getActiveClientSessions", query = "select COUNT(s) from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
+ @NamedQuery(name = "removeClientUserSessionByClient", query = "delete from ClientUserSessionAssociationEntity s where s.clientId = :clientId"),
+ @NamedQuery(name = "removeClientUserSessionByUser", query = "delete from ClientUserSessionAssociationEntity s where s.userId = :userId"),
+ @NamedQuery(name = "removeClientUserSessionByRealm", query = "delete from ClientUserSessionAssociationEntity s where s.realmId = :realmId")})
+public class ClientUserSessionAssociationEntity {
+ @Id
+ @GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.sessions.jpa.utils.JpaIdGenerator")
+ @GeneratedValue(generator = "uuid_generator")
+ private String id;
+
+ // we use ids to avoid select for update contention
+ private String userId;
+ private String realmId;
+
+ @ManyToOne(fetch= FetchType.LAZY)
+ private UserSessionEntity session;
+
+
+
+ private String clientId;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public UserSessionEntity getSession() {
+ return session;
+ }
+
+ public void setSession(UserSessionEntity session) {
+ this.session = session;
+ }
+
+ public String getClientId() {
+ return clientId;
+ }
+
+ public void setClientId(String clientId) {
+ this.clientId = clientId;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public void setUserId(String userId) {
+ this.userId = userId;
+ }
+
+ public String getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java
new file mode 100755
index 0000000..a550e8f
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UsernameLoginFailureEntity.java
@@ -0,0 +1,86 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@NamedQueries({
+ @NamedQuery(name="getAllFailures", query="select failure from UsernameLoginFailureEntity failure"),
+})
+public class UsernameLoginFailureEntity {
+ // we manually set the id to be username-realmid
+ // we may have a concurrent creation of the same login failure entry that we want to avoid
+ @Id
+ protected String id;
+ protected String username;
+ protected int failedLoginNotBefore;
+ protected int numFailures;
+ protected long lastFailure;
+ protected String lastIPFailure;
+
+ protected String realm;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public int getFailedLoginNotBefore() {
+ return failedLoginNotBefore;
+ }
+
+ public void setFailedLoginNotBefore(int failedLoginNotBefore) {
+ this.failedLoginNotBefore = failedLoginNotBefore;
+ }
+
+ public int getNumFailures() {
+ return numFailures;
+ }
+
+ public void setNumFailures(int numFailures) {
+ this.numFailures = numFailures;
+ }
+
+ public long getLastFailure() {
+ return lastFailure;
+ }
+
+ public void setLastFailure(long lastFailure) {
+ this.lastFailure = lastFailure;
+ }
+
+ public String getLastIPFailure() {
+ return lastIPFailure;
+ }
+
+ public void setLastIPFailure(String lastIPFailure) {
+ this.lastIPFailure = lastIPFailure;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
new file mode 100755
index 0000000..444afbe
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java
@@ -0,0 +1,105 @@
+package org.keycloak.models.sessions.jpa.entities;
+
+import org.hibernate.annotations.GenericGenerator;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Entity
+@NamedQueries({
+ @NamedQuery(name = "getUserSessionByUser", query = "select s from UserSessionEntity s where s.userId = :userId"),
+ @NamedQuery(name = "removeRealmUserSessions", query = "delete from UserSessionEntity s where s.realmId = :realmId"),
+ @NamedQuery(name = "removeUserSessionByUser", query = "delete from UserSessionEntity s where s.userId = :userId"),
+ @NamedQuery(name = "getUserSessionExpired", query = "select s from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime"),
+ @NamedQuery(name = "removeUserSessionExpired", query = "delete from UserSessionEntity s where s.started < :maxTime or s.lastSessionRefresh < :idleTime")
+})
+public class UserSessionEntity {
+
+ @Id
+ @GenericGenerator(name="uuid_generator", strategy="org.keycloak.models.sessions.jpa.utils.JpaIdGenerator")
+ @GeneratedValue(generator = "uuid_generator")
+ private String id;
+
+ // we use ids to avoid select for update contention
+ private String userId;
+ private String realmId;
+
+ private String ipAddress;
+
+ private int started;
+
+ private int lastSessionRefresh;
+
+ @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy="session")
+ private Collection<ClientUserSessionAssociationEntity> clients = new ArrayList<ClientUserSessionAssociationEntity>();
+
+
+ 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 getRealmId() {
+ return realmId;
+ }
+
+ public void setRealmId(String realmId) {
+ this.realmId = realmId;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public int getStarted() {
+ return started;
+ }
+
+ public void setStarted(int started) {
+ this.started = started;
+ }
+
+ public int getLastSessionRefresh() {
+ return lastSessionRefresh;
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ this.lastSessionRefresh = lastSessionRefresh;
+ }
+
+ public Collection<ClientUserSessionAssociationEntity> getClients() {
+ return clients;
+ }
+
+ public void setClients(Collection<ClientUserSessionAssociationEntity> clients) {
+ this.clients = clients;
+ }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaKeycloakTransaction.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaKeycloakTransaction.java
new file mode 100755
index 0000000..09f92c0
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaKeycloakTransaction.java
@@ -0,0 +1,53 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.keycloak.models.KeycloakTransaction;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaKeycloakTransaction implements KeycloakTransaction {
+
+ protected EntityManager em;
+
+ public JpaKeycloakTransaction(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public void begin() {
+ em.getTransaction().begin();
+ }
+
+ @Override
+ public void commit() {
+ try {
+ em.getTransaction().commit();
+ } catch (PersistenceException e) {
+ throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
+ }
+ }
+
+ @Override
+ public void rollback() {
+ em.getTransaction().rollback();
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ em.getTransaction().setRollbackOnly();
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return em.getTransaction().getRollbackOnly();
+ }
+
+ @Override
+ public boolean isActive() {
+ return em.getTransaction().isActive();
+ }
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProvider.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProvider.java
new file mode 100644
index 0000000..9511fcd
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProvider.java
@@ -0,0 +1,155 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.sessions.LoginFailure;
+import org.keycloak.models.sessions.Session;
+import org.keycloak.models.sessions.SessionProvider;
+import org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity;
+import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
+import org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity;
+import org.keycloak.util.Time;
+
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaSessionProvider implements SessionProvider {
+
+ protected final EntityManager em;
+
+ public JpaSessionProvider(EntityManager em) {
+ this.em = PersistenceExceptionConverter.create(em);
+ }
+
+ @Override
+ public LoginFailure getUserLoginFailure(String username, String realm) {
+ String id = username + "-" + realm;
+ UsernameLoginFailureEntity entity = em.find(UsernameLoginFailureEntity.class, id);
+ if (entity == null) return null;
+ return new UsernameLoginFailureAdapter(entity);
+ }
+
+ @Override
+ public LoginFailure addUserLoginFailure(String username, String realm) {
+ LoginFailure model = getUserLoginFailure(username, realm);
+ if (model != null) return model;
+ String id = username + "-" + realm;
+ UsernameLoginFailureEntity entity = new UsernameLoginFailureEntity();
+ entity.setId(id);
+ entity.setUsername(username);
+ entity.setRealm(realm);
+ em.persist(entity);
+ return new UsernameLoginFailureAdapter(entity);
+ }
+
+ @Override
+ public List<LoginFailure> getAllUserLoginFailures(String realm) {
+ TypedQuery<UsernameLoginFailureEntity> query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class);
+ List<UsernameLoginFailureEntity> entities = query.getResultList();
+ List<LoginFailure> models = new ArrayList<LoginFailure>();
+ for (UsernameLoginFailureEntity entity : entities) {
+ models.add(new UsernameLoginFailureAdapter(entity));
+ }
+ return models;
+ }
+
+ @Override
+ public Session createUserSession(String realm, String id, String user, String ipAddress) {
+ UserSessionEntity entity = new UserSessionEntity();
+ entity.setRealmId(realm);
+ entity.setUserId(user);
+ entity.setIpAddress(ipAddress);
+
+ int currentTime = Time.currentTime();
+
+ entity.setStarted(currentTime);
+ entity.setLastSessionRefresh(currentTime);
+
+ em.persist(entity);
+ return new UserSessionAdapter(em, realm, entity);
+ }
+
+ @Override
+ public Session getUserSession(String id, String realm) {
+ UserSessionEntity entity = em.find(UserSessionEntity.class, id);
+ return entity != null ? new UserSessionAdapter(em, realm, entity) : null;
+ }
+
+ @Override
+ public List<Session> getUserSessionsByUser(String user, String realm) {
+ List<Session> sessions = new LinkedList<Session>();
+ for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class)
+ .setParameter("userId", user).getResultList()) {
+ sessions.add(new UserSessionAdapter(em, realm, e));
+ }
+ return sessions;
+ }
+
+ @Override
+ public Set<Session> getUserSessionsByClient(String realm, String client) {
+ Set<Session> list = new HashSet<Session>();
+ TypedQuery<ClientUserSessionAssociationEntity> query = em.createNamedQuery("getClientUserSessionByClient", ClientUserSessionAssociationEntity.class);
+ query.setParameter("clientId", client);
+ List<ClientUserSessionAssociationEntity> results = query.getResultList();
+ for (ClientUserSessionAssociationEntity entity : results) {
+ list.add(new UserSessionAdapter(em, realm, entity.getSession()));
+ }
+ return list;
+ }
+
+ @Override
+ public int getActiveUserSessions(String realm, String client) {
+ Query query = em.createNamedQuery("getActiveClientSessions");
+ query.setParameter("clientId", client);
+ Object count = query.getSingleResult();
+ return ((Number)count).intValue();
+ }
+
+ @Override
+ public void removeUserSession(Session session) {
+ em.remove(((UserSessionAdapter) session).getEntity());
+ }
+
+ @Override
+ public void removeUserSessions(String realm, String user) {
+ em.createNamedQuery("removeClientUserSessionByUser").setParameter("userId", user).executeUpdate();
+ em.createNamedQuery("removeUserSessionByUser").setParameter("userId", user).executeUpdate();
+ }
+
+ @Override
+ public void removeExpiredUserSessions(String realm, long refreshTimeout, long sessionTimeout) {
+ TypedQuery<UserSessionEntity> query = em.createNamedQuery("getUserSessionExpired", UserSessionEntity.class)
+ .setParameter("maxTime", sessionTimeout)
+ .setParameter("idleTime", refreshTimeout);
+ List<UserSessionEntity> results = query.getResultList();
+ for (UserSessionEntity entity : results) {
+ em.remove(entity);
+ }
+ }
+
+ @Override
+ public void removeUserSessions(String realm) {
+ em.createNamedQuery("removeClientUserSessionByRealm").setParameter("realmId", realm).executeUpdate();
+ em.createNamedQuery("removeRealmUserSessions").setParameter("realmId", realm).executeUpdate();
+ }
+
+ @Override
+ public KeycloakTransaction getTransaction() {
+ return new JpaKeycloakTransaction(em);
+ }
+
+ @Override
+ public void close() {
+ if (em.getTransaction().isActive()) em.getTransaction().rollback();
+ if (em.isOpen()) em.close();
+ }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProviderFactory.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProviderFactory.java
new file mode 100644
index 0000000..c0c0cde
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/JpaSessionProviderFactory.java
@@ -0,0 +1,40 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.sessions.SessionProvider;
+import org.keycloak.models.sessions.SessionProviderFactory;
+import org.keycloak.util.JpaUtils;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaSessionProviderFactory implements SessionProviderFactory {
+
+ protected EntityManagerFactory emf;
+
+ @Override
+ public void init(Config.Scope config) {
+ String persistenceUnit = config.get("persistenceUnit", "jpa-keycloak-identity-store");
+ emf = Persistence.createEntityManagerFactory(persistenceUnit, JpaUtils.getHibernateProperties());
+ }
+
+ @Override
+ public String getId() {
+ return "jpa";
+ }
+
+ @Override
+ public SessionProvider create(KeycloakSession session) {
+ return new JpaSessionProvider(emf.createEntityManager());
+ }
+
+ @Override
+ public void close() {
+ emf.close();
+ }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/PersistenceExceptionConverter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/PersistenceExceptionConverter.java
new file mode 100644
index 0000000..2d27df0
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/PersistenceExceptionConverter.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.hibernate.exception.ConstraintViolationException;
+import org.keycloak.models.ModelDuplicateException;
+import org.keycloak.models.ModelException;
+
+import javax.persistence.EntityExistsException;
+import javax.persistence.EntityManager;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PersistenceExceptionConverter implements InvocationHandler {
+
+ private EntityManager em;
+
+ public static EntityManager create(EntityManager em) {
+ return (EntityManager) Proxy.newProxyInstance(EntityManager.class.getClassLoader(), new Class[]{EntityManager.class}, new PersistenceExceptionConverter(em));
+ }
+
+ private PersistenceExceptionConverter(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ return method.invoke(em, args);
+ } catch (InvocationTargetException e) {
+ throw convert(e.getCause());
+ }
+ }
+
+ public static ModelException convert(Throwable t) {
+ if (t.getCause() != null && t.getCause() instanceof ConstraintViolationException) {
+ throw new ModelDuplicateException(t);
+ } if (t instanceof EntityExistsException) {
+ throw new ModelDuplicateException(t);
+ } else {
+ throw new ModelException(t);
+ }
+ }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UsernameLoginFailureAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UsernameLoginFailureAdapter.java
new file mode 100755
index 0000000..8593096
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UsernameLoginFailureAdapter.java
@@ -0,0 +1,69 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.keycloak.models.sessions.LoginFailure;
+import org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UsernameLoginFailureAdapter implements LoginFailure
+{
+ protected UsernameLoginFailureEntity user;
+
+ public UsernameLoginFailureAdapter(UsernameLoginFailureEntity user) {
+ this.user = user;
+ }
+
+ @Override
+ public String getUsername()
+ {
+ return user.getUsername();
+ }
+
+ @Override
+ public int getFailedLoginNotBefore() {
+ return user.getFailedLoginNotBefore();
+ }
+
+ @Override
+ public void setFailedLoginNotBefore(int notBefore) {
+ user.setFailedLoginNotBefore(notBefore);
+ }
+
+ @Override
+ public int getNumFailures() {
+ return user.getNumFailures();
+ }
+
+ @Override
+ public void incrementFailures() {
+ user.setNumFailures(getNumFailures() + 1);
+ }
+
+ @Override
+ public void clearFailures() {
+ user.setNumFailures(0);
+ }
+
+ @Override
+ public long getLastFailure() {
+ return user.getLastFailure();
+ }
+
+ @Override
+ public void setLastFailure(long lastFailure) {
+ user.setLastFailure(lastFailure);
+ }
+
+ @Override
+ public String getLastIPFailure() {
+ return user.getLastIPFailure();
+ }
+
+ @Override
+ public void setLastIPFailure(String ip) {
+ user.setLastIPFailure(ip);
+ }
+
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
new file mode 100755
index 0000000..37e93bd
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java
@@ -0,0 +1,122 @@
+package org.keycloak.models.sessions.jpa;
+
+import org.keycloak.models.UserSessionModel;
+import org.keycloak.models.sessions.Session;
+import org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity;
+import org.keycloak.models.sessions.jpa.entities.UserSessionEntity;
+
+import javax.persistence.EntityManager;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class UserSessionAdapter implements Session {
+
+ private String realm;
+ private UserSessionEntity entity;
+ private EntityManager em;
+
+ public UserSessionAdapter(EntityManager em, String realm, UserSessionEntity entity) {
+ this.entity = entity;
+ this.em = em;
+ this.realm = realm;
+ }
+
+ public UserSessionEntity getEntity() {
+ return entity;
+ }
+
+ @Override
+ public String getId() {
+ return entity.getId();
+ }
+
+ @Override
+ public void setId(String id) {
+ entity.setId(id);
+ }
+
+ @Override
+ public String getUser() {
+ return entity.getUserId();
+ }
+
+ @Override
+ public void setUser(String user) {
+ entity.setUserId(user);
+ }
+
+ @Override
+ public String getIpAddress() {
+ return entity.getIpAddress();
+ }
+
+ @Override
+ public void setIpAddress(String ipAddress) {
+ entity.setIpAddress(ipAddress);
+ }
+
+ @Override
+ public int getStarted() {
+ return entity.getStarted();
+ }
+
+ @Override
+ public void setStarted(int started) {
+ entity.setStarted(started);
+ }
+
+ @Override
+ public int getLastSessionRefresh() {
+ return entity.getLastSessionRefresh();
+ }
+
+ @Override
+ public void setLastSessionRefresh(int seconds) {
+ entity.setLastSessionRefresh(seconds);
+ }
+
+ @Override
+ public void associateClient(String client) {
+ for (ClientUserSessionAssociationEntity ass : entity.getClients()) {
+ if (ass.getClientId().equals(client)) return;
+ }
+ ClientUserSessionAssociationEntity association = new ClientUserSessionAssociationEntity();
+ association.setClientId(client);
+ association.setSession(entity);
+ association.setUserId(entity.getUserId());
+ association.setRealmId(realm);
+ em.persist(association);
+ entity.getClients().add(association);
+ }
+
+ @Override
+ public void removeAssociatedClient(String client) {
+ em.createNamedQuery("removeClientUserSessionByClient").setParameter("clientId", client).executeUpdate();
+ }
+
+ @Override
+ public List<String> getClientAssociations() {
+ List<String> clients = new ArrayList<String>();
+ for (ClientUserSessionAssociationEntity association : entity.getClients()) {
+ clients.add(association.getClientId());
+ }
+ return clients;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof UserSessionModel)) return false;
+
+ UserSessionModel that = (UserSessionModel) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+}
diff --git a/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/utils/JpaIdGenerator.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/utils/JpaIdGenerator.java
new file mode 100644
index 0000000..3fc63e7
--- /dev/null
+++ b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/utils/JpaIdGenerator.java
@@ -0,0 +1,19 @@
+package org.keycloak.models.sessions.jpa.utils;
+
+import org.hibernate.HibernateException;
+import org.hibernate.engine.spi.SessionImplementor;
+import org.hibernate.id.IdentifierGenerator;
+import org.keycloak.models.utils.KeycloakModelUtils;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class JpaIdGenerator implements IdentifierGenerator {
+
+ @Override
+ public Serializable generate(SessionImplementor session, Object object) throws HibernateException {
+ return KeycloakModelUtils.generateId();
+ }
+}
diff --git a/model/sessions-jpa/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory b/model/sessions-jpa/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory
new file mode 100644
index 0000000..c086c02
--- /dev/null
+++ b/model/sessions-jpa/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.sessions.jpa.JpaSessionProviderFactory
\ No newline at end of file
model/sessions-mem/pom.xml 35(+35 -0)
diff --git a/model/sessions-mem/pom.xml b/model/sessions-mem/pom.xml
new file mode 100755
index 0000000..322fc5f
--- /dev/null
+++ b/model/sessions-mem/pom.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-model-sessions-mem</artifactId>
+ <name>Keycloak Model Sessions Mem</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureAdapter.java
new file mode 100644
index 0000000..f9054b5
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureAdapter.java
@@ -0,0 +1,81 @@
+package org.keycloak.models.sessions.mem;
+
+import org.keycloak.models.sessions.LoginFailure;
+
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginFailureAdapter implements LoginFailure {
+
+ private final String username;
+ private final String realm;
+
+ private AtomicInteger failedLoginNotBefore = new AtomicInteger();
+ private AtomicInteger numFailures = new AtomicInteger();
+ private AtomicLong lastFailure = new AtomicLong();
+ private AtomicReference<String> lastIpFailure = new AtomicReference<String>();
+
+ public LoginFailureAdapter(String username, String realm) {
+ this.username = username;
+ this.realm = realm;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ @Override
+ public int getFailedLoginNotBefore() {
+ return failedLoginNotBefore.get();
+ }
+
+ @Override
+ public void setFailedLoginNotBefore(int notBefore) {
+ failedLoginNotBefore.set(notBefore);
+ }
+
+ @Override
+ public int getNumFailures() {
+ return numFailures.get();
+ }
+
+ @Override
+ public void incrementFailures() {
+ numFailures.incrementAndGet();
+ }
+
+ @Override
+ public void clearFailures() {
+ numFailures.set(0);
+ }
+
+ @Override
+ public long getLastFailure() {
+ return lastFailure.get();
+ }
+
+ @Override
+ public void setLastFailure(long lastFailure) {
+ this.lastFailure.set(lastFailure);
+ }
+
+ @Override
+ public String getLastIPFailure() {
+ return lastIpFailure.get();
+ }
+
+ @Override
+ public void setLastIPFailure(String ip) {
+ lastIpFailure.set(ip);
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureKey.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureKey.java
new file mode 100644
index 0000000..b0ab77b
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/LoginFailureKey.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.sessions.mem;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class LoginFailureKey {
+
+ private final String realm;
+ private final String username;
+
+ public LoginFailureKey(String realm, String username) {
+ this.realm = realm;
+ this.username = username;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ LoginFailureKey key = (LoginFailureKey) o;
+
+ if (realm != null ? !realm.equals(key.realm) : key.realm != null) return false;
+ if (username != null ? !username.equals(key.username) : key.username != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = realm != null ? realm.hashCode() : 0;
+ result = 31 * result + (username != null ? username.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProvider.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProvider.java
new file mode 100644
index 0000000..031447d
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProvider.java
@@ -0,0 +1,194 @@
+package org.keycloak.models.sessions.mem;
+
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.sessions.LoginFailure;
+import org.keycloak.models.sessions.Session;
+import org.keycloak.models.sessions.SessionProvider;
+import org.keycloak.util.Time;
+
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MemSessionProvider implements SessionProvider {
+
+ private final ConcurrentHashMap<SessionKey, SessionAdapter> sessions;
+ private final ConcurrentHashMap<LoginFailureKey, LoginFailureAdapter> loginFailures;
+ private DummyKeycloakTransaction tx;
+
+ public MemSessionProvider(ConcurrentHashMap<SessionKey, SessionAdapter> sessions, ConcurrentHashMap<LoginFailureKey, LoginFailureAdapter> loginFailures) {
+ this.sessions = sessions;
+ this.loginFailures = loginFailures;
+ }
+
+ @Override
+ public LoginFailure getUserLoginFailure(String username, String realm) {
+ return loginFailures.get(new LoginFailureKey(username, realm));
+ }
+
+ @Override
+ public LoginFailure addUserLoginFailure(String username, String realm) {
+ LoginFailureKey key = new LoginFailureKey(username, realm);
+ return loginFailures.putIfAbsent(key, new LoginFailureAdapter(username, realm));
+ }
+
+ @Override
+ public List<LoginFailure> getAllUserLoginFailures(String realm) {
+ List<LoginFailure> failures = new LinkedList<LoginFailure>();
+ for (LoginFailureAdapter failure : loginFailures.values()) {
+ if (failure.getRealm().equals(realm)) {
+ failures.add(failure);
+ }
+ }
+ return failures;
+ }
+
+ @Override
+ public Session createUserSession(String realm, String id, String user, String ipAddress) {
+ SessionAdapter adapter = new SessionAdapter();
+ adapter.setRealm(realm);
+ adapter.setId(id);
+ adapter.setUser(user);
+ adapter.setIpAddress(ipAddress);
+
+ int currentTime = Time.currentTime();
+
+ adapter.setStarted(currentTime);
+ adapter.setLastSessionRefresh(currentTime);
+
+ sessions.put(new SessionKey(realm, id), adapter);
+ return adapter;
+ }
+
+ @Override
+ public Session getUserSession(String id, String realm) {
+ return sessions.get(new SessionKey(realm, id));
+ }
+
+ @Override
+ public List<Session> getUserSessionsByUser(String user, String realm) {
+ List<Session> userSessions = new LinkedList<Session>();
+ for (SessionAdapter s : sessions.values()) {
+ if (s.getRealm().equals(realm) && s.getUser().equals(user)) {
+ userSessions.add(s);
+ }
+ }
+ return userSessions;
+ }
+
+ @Override
+ public Set<Session> getUserSessionsByClient(String realm, String client) {
+ Set<Session> clientSessions = new HashSet<Session>();
+ for (SessionAdapter s : sessions.values()) {
+ if (s.getRealm().equals(realm) && s.getClientAssociations().contains(client)) {
+ clientSessions.add(s);
+ }
+ }
+ return clientSessions;
+ }
+
+ @Override
+ public int getActiveUserSessions(String realm, String client) {
+ int count = 0;
+ for (SessionAdapter s : sessions.values()) {
+ if (s.getRealm().equals(realm) && s.getClientAssociations().contains(client)) {
+ count++;
+ }
+ }
+ return count;
+ }
+
+ @Override
+ public void removeUserSession(Session session) {
+ String realm = ((SessionAdapter) session).getRealm();
+ sessions.remove(new SessionKey(realm, session.getId()));
+ }
+
+ @Override
+ public void removeUserSessions(String realm, String user) {
+ Iterator<SessionAdapter> itr = sessions.values().iterator();
+ while (itr.hasNext()) {
+ SessionAdapter s = itr.next();
+ if (s.getRealm().equals(realm) && s.getUser().equals(user)) {
+ itr.remove();
+ }
+ }
+ }
+
+ @Override
+ public void removeExpiredUserSessions(String realm, long refreshTimeout, long sessionTimeout) {
+ Iterator<SessionAdapter> itr = sessions.values().iterator();
+ while (itr.hasNext()) {
+ SessionAdapter s = itr.next();
+ if (s.getLastSessionRefresh() < refreshTimeout || s.getStarted() < sessionTimeout) {
+ itr.remove();
+ }
+ }
+ }
+
+ @Override
+ public void removeUserSessions(String realm) {
+ Iterator<SessionAdapter> itr = sessions.values().iterator();
+ while (itr.hasNext()) {
+ SessionAdapter s = itr.next();
+ if (s.getRealm().equals(realm)) {
+ itr.remove();
+ }
+ }
+ }
+
+ @Override
+ public KeycloakTransaction getTransaction() {
+ if (tx == null) {
+ tx = new DummyKeycloakTransaction();
+ }
+ return tx;
+ }
+
+ @Override
+ public void close() {
+ }
+
+ public static class DummyKeycloakTransaction implements KeycloakTransaction {
+
+ public boolean rollBackOnly;
+ public boolean active;
+
+ @Override
+ public void begin() {
+ this.active = true;
+ }
+
+ @Override
+ public void commit() {
+ }
+
+ @Override
+ public void rollback() {
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ this.rollBackOnly = true;
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return rollBackOnly;
+ }
+
+ @Override
+ public boolean isActive() {
+ return active;
+ }
+
+
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProviderFactory.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProviderFactory.java
new file mode 100644
index 0000000..7eccae0
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/MemSessionProviderFactory.java
@@ -0,0 +1,43 @@
+package org.keycloak.models.sessions.mem;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.sessions.LoginFailure;
+import org.keycloak.models.sessions.SessionProvider;
+import org.keycloak.models.sessions.SessionProviderFactory;
+
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class MemSessionProviderFactory implements SessionProviderFactory {
+
+ private ConcurrentHashMap<SessionKey, SessionAdapter> sessions = new ConcurrentHashMap<SessionKey, SessionAdapter>();
+ private ConcurrentHashMap<LoginFailureKey, LoginFailureAdapter> loginFailures = new ConcurrentHashMap<LoginFailureKey, LoginFailureAdapter>();
+
+ @Override
+ public SessionProvider create(KeycloakSession session) {
+ return new MemSessionProvider(sessions, loginFailures);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ }
+
+ @Override
+ public void close() {
+ sessions.clear();
+ loginFailures.clear();
+ }
+
+ @Override
+ public String getId() {
+ return "mem";
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/SessionAdapter.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/SessionAdapter.java
new file mode 100644
index 0000000..0dbd47d
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/SessionAdapter.java
@@ -0,0 +1,86 @@
+package org.keycloak.models.sessions.mem;
+
+import org.keycloak.models.sessions.Session;
+
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SessionAdapter implements Session {
+
+ private String realm;
+ private String id;
+ private String user;
+ private String ipAddress;
+ private int started;
+ private int lastSessionRefresh;
+ private List<String> clients = new LinkedList<String>();
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUser() {
+ return user;
+ }
+
+ public void setUser(String user) {
+ this.user = user;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public int getStarted() {
+ return started;
+ }
+
+ public void setStarted(int started) {
+ this.started = started;
+ }
+
+ public int getLastSessionRefresh() {
+ return lastSessionRefresh;
+ }
+
+ public void setLastSessionRefresh(int lastSessionRefresh) {
+ this.lastSessionRefresh = lastSessionRefresh;
+ }
+
+ @Override
+ public void associateClient(String client) {
+ if (!clients.contains(client)) {
+ clients.add(client);
+ }
+ }
+
+ @Override
+ public List<String> getClientAssociations() {
+ return clients;
+ }
+
+ @Override
+ public void removeAssociatedClient(String client) {
+ clients.remove(client);
+ }
+
+}
diff --git a/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/SessionKey.java b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/SessionKey.java
new file mode 100644
index 0000000..fb0d4e7
--- /dev/null
+++ b/model/sessions-mem/src/main/java/org/keycloak/models/sessions/mem/SessionKey.java
@@ -0,0 +1,36 @@
+package org.keycloak.models.sessions.mem;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class SessionKey {
+
+ private final String realm;
+ private final String id;
+
+ public SessionKey(String realm, String id) {
+ this.realm = realm;
+ this.id = id;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ SessionKey key = (SessionKey) o;
+
+ if (realm != null ? !realm.equals(key.realm) : key.realm != null) return false;
+ if (id != null ? !id.equals(key.id) : key.id != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = realm != null ? realm.hashCode() : 0;
+ result = 31 * result + (id != null ? id.hashCode() : 0);
+ return result;
+ }
+
+}
diff --git a/model/sessions-mem/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory b/model/sessions-mem/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory
new file mode 100644
index 0000000..407b73d
--- /dev/null
+++ b/model/sessions-mem/src/main/resources/META-INF/services/org.keycloak.models.sessions.SessionProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.sessions.mem.MemSessionProviderFactory
\ No newline at end of file
model/tests-hybrid/pom.xml 55(+55 -0)
diff --git a/model/tests-hybrid/pom.xml b/model/tests-hybrid/pom.xml
new file mode 100755
index 0000000..8db100c
--- /dev/null
+++ b/model/tests-hybrid/pom.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-model-tests-hybrid</artifactId>
+ <name>Keycloak Model Tests</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ <scope>compile</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>compile</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/model/tests-hybrid/src/main/java/org/keycloak/model/test/AbstractUserProviderTest.java b/model/tests-hybrid/src/main/java/org/keycloak/model/test/AbstractUserProviderTest.java
new file mode 100644
index 0000000..361b953
--- /dev/null
+++ b/model/tests-hybrid/src/main/java/org/keycloak/model/test/AbstractUserProviderTest.java
@@ -0,0 +1,274 @@
+package org.keycloak.model.test;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.keycloak.Config;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.users.Credentials;
+import org.keycloak.models.users.Feature;
+import org.keycloak.models.users.User;
+import org.keycloak.models.users.UserProvider;
+import org.keycloak.models.users.UserProviderFactory;
+import org.keycloak.models.users.UserSpi;
+import org.keycloak.models.utils.Pbkdf2PasswordEncoder;
+import org.keycloak.provider.ProviderFactory;
+
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.Set;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public abstract class AbstractUserProviderTest {
+
+ private ProviderFactory<UserProvider> factory;
+ private UserProvider provider;
+ private String userId;
+ private String userId2;
+ private String userId3;
+
+ @Before
+ public void before() {
+ String providerId = getProviderId();
+ ServiceLoader<UserProviderFactory> factories = ServiceLoader.load(UserProviderFactory.class);
+ for (UserProviderFactory f : factories) {
+ if (f.getId().equals(providerId)) {
+ factory = f;
+ factory.init(Config.scope(new UserSpi().getName(), providerId));
+ }
+ }
+
+ provider = factory.create(null);
+
+ userId = "1";
+ userId2 = "2";
+ userId3 = "1";
+ }
+
+ @After
+ public void after() {
+ provider.getTransaction().begin();
+ provider.onRealmRemoved("test-realm");
+ provider.getTransaction().commit();
+ }
+
+ protected abstract String getProviderId();
+
+ @Test
+ public void persistUsers() {
+ provider.getTransaction().begin();
+
+ Set<String> roles = new HashSet<String>();
+ roles.add("a");
+ roles.add("a1");
+
+ User user = provider.addUser(userId, "user", roles, "test-realm");
+ user.setFirstName("first-name");
+ user.setLastName("last-name");
+ user.setEmail("email");
+ user.setAttribute("a", "a1");
+ user.setAttribute("b", "b1");
+
+ Set<String> roles2 = new HashSet<String>();
+ roles2.add("a");
+ roles2.add("a2");
+
+ User user2 = provider.addUser(userId2, "user2", roles2, "test-realm");
+ user2.setFirstName("first-name2");
+ user2.setLastName("last-name2");
+ user2.setEmail("email2");
+ user2.setAttribute("a", "a2");
+ user2.setAttribute("b", "b2");
+
+ User user3 = provider.addUser(userId3, "user", roles2, "test-realm2");
+ user3.setFirstName("first-name");
+ user3.setLastName("last-name");
+ user3.setEmail("email");
+ user3.setAttribute("a", "a1");
+ user3.setAttribute("b", "b1");
+
+ provider.getTransaction().commit();
+
+ User persisted = provider.getUserById(userId, "test-realm");
+ User persisted2 = provider.getUserById(userId2, "test-realm");
+ User persisted3 = provider.getUserById(userId3, "test-realm2");
+
+ assertUser(user, persisted);
+ assertUser(user2, persisted2);
+ assertUser(user3, persisted3);
+ }
+
+ @Test
+ public void getUserByEmail() {
+ persistUsers();
+
+ assertEquals(userId, provider.getUserByEmail("email", "test-realm").getId());
+ assertEquals(userId2, provider.getUserByEmail("email2", "test-realm").getId());
+ }
+
+ @Test
+ public void getUserByUsername() {
+ persistUsers();
+
+ assertEquals(userId, provider.getUserByUsername("user", "test-realm").getId());
+ assertEquals(userId2, provider.getUserByUsername("user2", "test-realm").getId());
+ }
+
+ @Test
+ public void readAndUpdateCredentials() {
+ if (provider.supports(Feature.READ_CREDENTIALS) && provider.supports(Feature.UPDATE_CREDENTIALS)) {
+ persistUsers();
+
+ provider.getTransaction().begin();
+
+ User user = provider.getUserById(userId, "test-realm");
+ User user2 = provider.getUserById(userId2, "test-realm");
+ User user3 = provider.getUserById(userId3, "test-realm2");
+
+ byte[] salt = Pbkdf2PasswordEncoder.getSalt();
+ Pbkdf2PasswordEncoder encoder = new Pbkdf2PasswordEncoder(salt);
+
+ user.updateCredential(new Credentials(UserCredentialModel.PASSWORD, salt, encoder.encode("password", 1000), 1000, null));
+ user.updateCredential(new Credentials(UserCredentialModel.TOTP, "totp-secret", null));
+
+ user2.updateCredential(new Credentials(UserCredentialModel.PASSWORD, salt, encoder.encode("password2", 1000), 1000, null));
+ user3.updateCredential(new Credentials(UserCredentialModel.PASSWORD, salt, encoder.encode("password3", 1000), 1000, null));
+
+ provider.getTransaction().commit();
+
+ User persisted = provider.getUserById(userId, "test-realm");
+ assertEquals(2, persisted.getCredentials().size());
+ assertPassword(persisted, "password");
+ assertTotp(user, "totp-secret");
+
+ User persisted2 = provider.getUserById(userId2, "test-realm");
+ assertEquals(1, persisted2.getCredentials().size());
+ assertPassword(persisted2, "password2");
+
+ User persisted3 = provider.getUserById(userId3, "test-realm2");
+ assertEquals(1, persisted3.getCredentials().size());
+ assertPassword(persisted3, "password3");
+ }
+ }
+
+ @Test
+ public void userSearch() throws Exception {
+ provider.getTransaction().begin();
+ {
+ User user = provider.addUser("bill-burke", "bill-burke", null, "test-realm");
+ user.setLastName("Burke");
+ user.setFirstName("Bill");
+ user.setEmail("bburke@redhat.com");
+
+ User user2 = provider.addUser("knut-ole", "knut-ole", null, "test-realm");
+ user2.setFirstName("Knut Ole");
+ user2.setLastName("Alver");
+ user2.setEmail("knut@redhat.com");
+
+ User user3 = provider.addUser("ole-alver", "ole-alver", null, "test-realm");
+ user3.setFirstName("Ole");
+ user3.setLastName("Alver Veland");
+ user3.setEmail("knut2@redhat.com");
+ }
+
+ provider.getTransaction().commit();
+
+ assertUsers(provider.searchForUser("total junk query", "test-realm"));
+ assertUsers(provider.searchForUser("Bill Burke", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("bill burk", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("bill burk", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("ole alver", "test-realm"), "knut-ole", "ole-alver");
+ assertUsers(provider.searchForUser("bburke@redhat.com", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("rke@redhat.com", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("bburke", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("BurK", "test-realm"), "bill-burke");
+ assertUsers(provider.searchForUser("Burke", "test-realm"), "bill-burke");
+
+ provider.getTransaction().begin();
+ {
+ User user = provider.addUser("monica-burke", "monica-burke", null, "test-realm");
+ user.setLastName("Burke");
+ user.setFirstName("Monica");
+ user.setEmail("mburke@redhat.com");
+ }
+
+ {
+ User user = provider.addUser("stian-thorgersen", "stian-thorgersen", null, "test-realm");
+ user.setLastName("Thorgersen");
+ user.setFirstName("Stian");
+ user.setEmail("thor@redhat.com");
+ }
+ provider.getTransaction().commit();
+
+ assertUsers(provider.searchForUser("Monica Burke", "test-realm"), "monica-burke");
+ assertUsers(provider.searchForUser("mburke@redhat.com", "test-realm"), "monica-burke");
+ assertUsers(provider.searchForUser("mburke", "test-realm"), "monica-burke");
+ assertUsers(provider.searchForUser("Burke", "test-realm"), "bill-burke", "monica-burke");
+
+ provider.getTransaction().begin();
+ provider.addUser("bill-burke", "bill-burke", null, "test-realm2");
+ provider.getTransaction().commit();
+
+ Assert.assertEquals(1, provider.getUsers("test-realm2").size());
+ assertUsers(provider.searchForUser("Burke", "test-realm2"), "bill-burke");
+ }
+
+ private static void assertUsers(List<User> users, String... expectedIds) {
+ if (expectedIds == null) {
+ expectedIds = new String[0];
+ }
+
+ String[] actualIds = new String[users.size()];
+ for (int i = 0; i < users.size(); i++) {
+ actualIds[i] = users.get(i).getId();
+ }
+
+ Arrays.sort(actualIds);
+ Arrays.sort(expectedIds);
+
+ assertArrayEquals(expectedIds, actualIds);
+ }
+
+ public static void assertUser(User expected, User actual) {
+ assertEquals(expected.getId(), actual.getId());
+ assertEquals(expected.getUsername(), actual.getUsername());
+ assertEquals(expected.getFirstName(), actual.getFirstName());
+ assertEquals(expected.getLastName(), actual.getLastName());
+ assertEquals(expected.getEmail(), actual.getEmail());
+
+ assertEquals(expected.getAttributes(), actual.getAttributes());
+ assertEquals(expected.getRoleMappings(), actual.getRoleMappings());
+ }
+
+ public static void assertPassword(User user, String expectedPassword) {
+ for (Credentials cred : user.getCredentials()) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ assertTrue("Invalid credentials", new Pbkdf2PasswordEncoder(cred.getSalt(), 1000).verify(expectedPassword, cred.getValue()));
+ return;
+ }
+ }
+ fail("Password credentials not found");
+ }
+
+ public static void assertTotp(User user, String expectedTotpSecret) {
+ for (Credentials cred : user.getCredentials()) {
+ if (cred.getType().equals(UserCredentialModel.TOTP)) {
+ assertEquals(expectedTotpSecret, cred.getValue());
+ return;
+ }
+ }
+ fail("Totp credentials not found");
+ }
+
+}
model/users-jpa/pom.xml 79(+79 -0)
diff --git a/model/users-jpa/pom.xml b/model/users-jpa/pom.xml
new file mode 100755
index 0000000..46ca527
--- /dev/null
+++ b/model/users-jpa/pom.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-model-users-jpa</artifactId>
+ <name>Keycloak Model Users JPA</name>
+ <description/>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-core</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate.javax.persistence</groupId>
+ <artifactId>hibernate-jpa-2.0-api</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hibernate</groupId>
+ <artifactId>hibernate-entitymanager</artifactId>
+ <version>${hibernate.entitymanager.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-tests-hybrid</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.h2database</groupId>
+ <artifactId>h2</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.bouncycastle</groupId>
+ <artifactId>bcprov-jdk16</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>net.iharder</groupId>
+ <artifactId>base64</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserAttributeEntity.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserAttributeEntity.java
new file mode 100644
index 0000000..c888b55
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserAttributeEntity.java
@@ -0,0 +1,79 @@
+package org.keycloak.models.users.jpa.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.ManyToOne;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Entity
+@IdClass(UserAttributeEntity.Key.class)
+public class UserAttributeEntity {
+
+ @Id
+ protected String name;
+
+ @Id
+ @ManyToOne
+ protected UserEntity user;
+
+ protected String value;
+
+ public UserAttributeEntity() {
+ }
+
+ public UserAttributeEntity(UserEntity user, String name, String value) {
+ this.user = user;
+ this.name = name;
+ this.value = value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public static class Key implements Serializable {
+ private String name;
+ private UserEntity user;
+
+ public Key() {
+ }
+
+ public Key(String name, UserEntity user) {
+ this.name = name;
+ this.user = user;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (name != null ? !name.equals(key.name) : key.name != null) return false;
+ if (user != null ? !user.equals(key.user) : key.user != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = name != null ? name.hashCode() : 0;
+ result = 31 * result + (user != null ? user.hashCode() : 0);
+ return result;
+ }
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserCredentialEntity.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserCredentialEntity.java
new file mode 100755
index 0000000..3d0175f
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserCredentialEntity.java
@@ -0,0 +1,107 @@
+package org.keycloak.models.users.jpa.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.ManyToOne;
+
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@Entity
+@IdClass(UserCredentialEntity.Key.class)
+public class UserCredentialEntity {
+
+ @Id
+ protected String type;
+
+ protected String value;
+ protected String device;
+ protected byte[] salt;
+ protected int hashIterations;
+
+ @Id
+ @ManyToOne
+ protected UserEntity user;
+
+ public UserCredentialEntity() {
+ }
+
+ public UserCredentialEntity(UserEntity user, String type) {
+ this.user = user;
+ this.type = type;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(String value) {
+ this.value = value;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public String getDevice() {
+ return device;
+ }
+
+ public void setDevice(String device) {
+ this.device = device;
+ }
+
+ public byte[] getSalt() {
+ return salt;
+ }
+
+ public void setSalt(byte[] salt) {
+ this.salt = salt;
+ }
+
+ public int getHashIterations() {
+ return hashIterations;
+ }
+
+ public void setHashIterations(int hashIterations) {
+ this.hashIterations = hashIterations;
+ }
+
+ public static class Key implements Serializable {
+ private String type;
+ private UserEntity user;
+
+ public Key() {
+ }
+
+ public Key(String type, UserEntity user) {
+ this.type = type;
+ this.user = user;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (type != null ? !type.equals(key.type) : key.type != null) return false;
+ if (user != null ? !user.equals(key.user) : key.user != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (user != null ? user.hashCode() : 0);
+ return result;
+ }
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserEntity.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserEntity.java
new file mode 100755
index 0000000..09f8788
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserEntity.java
@@ -0,0 +1,179 @@
+package org.keycloak.models.users.jpa.entities;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.UniqueConstraint;
+import java.io.Serializable;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+@NamedQueries({
+ @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="getRealmUserByAttribute", query="select u from UserEntity u join u.attributes a where u.realm = :realm and a.name = :name and a.value = :value")
+})
+@Entity
+@Table(uniqueConstraints = {
+ @UniqueConstraint(columnNames = { "realm", "username" }),
+ @UniqueConstraint(columnNames = { "realm", "emailConstraint" })
+})
+@IdClass(UserEntity.Key.class)
+public class UserEntity {
+ @Id
+ protected String id;
+
+ protected boolean enabled;
+ protected String username;
+ protected String firstName;
+ protected String lastName;
+ protected String email;
+
+ // Hack just to workaround the fact that on MS-SQL you can't have unique constraint with multiple NULL values TODO: Find better solution (like unique index with 'where' but that's proprietary)
+ protected String emailConstraint;
+
+ @Id
+ protected String realm;
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy="user")
+ protected List<UserAttributeEntity> attributes = new LinkedList<UserAttributeEntity>();
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy="user")
+ protected List<UserCredentialEntity> credentials = new LinkedList<UserCredentialEntity>();
+
+ @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy="user")
+ protected List<UserRoleMappingEntity> roles = new LinkedList<UserRoleMappingEntity>();
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getFirstName() {
+ return firstName;
+ }
+
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ public String getLastName() {
+ return lastName;
+ }
+
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ public String getEmail() {
+ return email;
+ }
+
+ public void setEmail(String email) {
+ this.email = email;
+ this.emailConstraint = email != null ? email : id;
+ }
+
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
+
+ public String getEmailConstraint() {
+ return emailConstraint;
+ }
+
+ public void setEmailConstraint(String emailConstraint) {
+ this.emailConstraint = emailConstraint;
+ }
+
+ public List<UserAttributeEntity> getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(List<UserAttributeEntity> attributes) {
+ this.attributes = attributes;
+ }
+
+ public String getRealm() {
+ return realm;
+ }
+
+ public void setRealm(String realm) {
+ this.realm = realm;
+ }
+
+ public Collection<UserCredentialEntity> getCredentials() {
+ return credentials;
+ }
+
+ public void setCredentials(List<UserCredentialEntity> credentials) {
+ this.credentials = credentials;
+ }
+
+ public List<UserRoleMappingEntity> getRoles() {
+ return roles;
+ }
+
+ public void setRoles(List<UserRoleMappingEntity> roles) {
+ this.roles = roles;
+ }
+
+ public static class Key implements Serializable {
+ private String id;
+ private String realm;
+
+ public Key() {
+ }
+
+ public Key(String id, String realm) {
+ this.id = id;
+ this.realm = realm;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (id != null ? !id.equals(key.id) : key.id != null) return false;
+ if (realm != null ? !realm.equals(key.realm) : key.realm != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (realm != null ? realm.hashCode() : 0);
+ return result;
+ }
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserRoleMappingEntity.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserRoleMappingEntity.java
new file mode 100644
index 0000000..513163e
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/entities/UserRoleMappingEntity.java
@@ -0,0 +1,68 @@
+package org.keycloak.models.users.jpa.entities;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.ManyToOne;
+import java.io.Serializable;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+@Entity
+@IdClass(UserRoleMappingEntity.Key.class)
+public class UserRoleMappingEntity {
+
+ @Id
+ protected String role;
+
+ @Id
+ @ManyToOne
+ protected UserEntity user;
+
+ public UserRoleMappingEntity() {
+ }
+
+ public UserRoleMappingEntity(UserEntity user, String role) {
+ this.user = user;
+ this.role = role;
+ }
+
+ public String getRole() {
+ return role;
+ }
+
+ public static class Key implements Serializable {
+ private String role;
+ private UserEntity user;
+
+ public Key() {
+ }
+
+ public Key(String role, UserEntity user) {
+ this.role = role;
+ this.user = user;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ Key key = (Key) o;
+
+ if (role != null ? !role.equals(key.role) : key.role != null) return false;
+ if (user != null ? !user.equals(key.user) : key.user != null) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = role != null ? role.hashCode() : 0;
+ result = 31 * result + (user != null ? user.hashCode() : 0);
+ return result;
+ }
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaKeycloakTransaction.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaKeycloakTransaction.java
new file mode 100755
index 0000000..2d1ab13
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaKeycloakTransaction.java
@@ -0,0 +1,53 @@
+package org.keycloak.models.users.jpa;
+
+import org.keycloak.models.KeycloakTransaction;
+
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceException;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaKeycloakTransaction implements KeycloakTransaction {
+
+ protected EntityManager em;
+
+ public JpaKeycloakTransaction(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public void begin() {
+ em.getTransaction().begin();
+ }
+
+ @Override
+ public void commit() {
+ try {
+ em.getTransaction().commit();
+ } catch (PersistenceException e) {
+ throw PersistenceExceptionConverter.convert(e.getCause() != null ? e.getCause() : e);
+ }
+ }
+
+ @Override
+ public void rollback() {
+ em.getTransaction().rollback();
+ }
+
+ @Override
+ public void setRollbackOnly() {
+ em.getTransaction().setRollbackOnly();
+ }
+
+ @Override
+ public boolean getRollbackOnly() {
+ return em.getTransaction().getRollbackOnly();
+ }
+
+ @Override
+ public boolean isActive() {
+ return em.getTransaction().isActive();
+ }
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaUserProvider.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaUserProvider.java
new file mode 100755
index 0000000..204ab33
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaUserProvider.java
@@ -0,0 +1,184 @@
+package org.keycloak.models.users.jpa;
+
+import org.keycloak.models.KeycloakTransaction;
+import org.keycloak.models.users.Credentials;
+import org.keycloak.models.users.Feature;
+import org.keycloak.models.users.User;
+import org.keycloak.models.users.UserProvider;
+import org.keycloak.models.users.jpa.entities.UserEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.TypedQuery;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserProvider implements UserProvider {
+
+ protected final EntityManager em;
+
+ public JpaUserProvider(EntityManager em) {
+ this.em = PersistenceExceptionConverter.create(em);
+ }
+
+ @Override
+ public KeycloakTransaction getTransaction() {
+ return new JpaKeycloakTransaction(em);
+ }
+
+ @Override
+ public User addUser(String id, String username, Set<String> initialRoles, String realm) {
+ UserEntity entity = new UserEntity();
+ entity.setId(id);
+ entity.setUsername(username);
+ entity.setEmailConstraint(id);
+ entity.setRealm(realm);
+ em.persist(entity);
+
+ UserAdapter adapter = new UserAdapter(realm, em, entity);
+
+ if (initialRoles != null && !initialRoles.isEmpty()) {
+ for (String role : initialRoles) {
+ adapter.grantRole(role);
+ }
+ }
+
+ return adapter;
+ }
+
+ @Override
+ public boolean removeUser(String name, String realm) {
+ TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByLoginName", UserEntity.class);
+ query.setParameter("username", name);
+ query.setParameter("realm", realm);
+ List<UserEntity> results = query.getResultList();
+ if (results.size() == 0) return false;
+ em.remove(results.get(0));
+ return true;
+ }
+
+ @Override
+ public User getUserById(String id, String realm) {
+ UserEntity user = em.find(UserEntity.class, new UserEntity.Key(id, realm));
+ return user != null ? new UserAdapter(realm, em, user) : null;
+ }
+
+ @Override
+ public User getUserByUsername(String username, String realm) {
+ TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByUsername", UserEntity.class);
+ query.setParameter("username", username);
+ query.setParameter("realm", realm);
+ List<UserEntity> results = query.getResultList();
+ if (results.size() == 0) return null;
+ return new UserAdapter(realm, em, results.get(0));
+ }
+
+ @Override
+ public User getUserByEmail(String email, String realm) {
+ TypedQuery<UserEntity> query = em.createNamedQuery("getRealmUserByEmail", UserEntity.class);
+ query.setParameter("email", email);
+ query.setParameter("realm", realm);
+ List<UserEntity> results = query.getResultList();
+ return results.isEmpty() ? null : new UserAdapter(realm, em, results.get(0));
+ }
+
+ @Override
+ public User getUserByAttribute(String name, String value, String realm) {
+ List<UserEntity> results = em.createNamedQuery("getRealmUserByAttribute", UserEntity.class)
+ .setParameter("realm", realm)
+ .setParameter("name", name)
+ .setParameter("value", value)
+ .getResultList();
+ return results.isEmpty() ? null : new UserAdapter(realm, em, results.get(0));
+ }
+
+ @Override
+ public void close() {
+ if (em.getTransaction().isActive()) em.getTransaction().rollback();
+ if (em.isOpen()) em.close();
+ }
+
+ @Override
+ public List<User> getUsers(String realm) {
+ TypedQuery<UserEntity> query = em.createQuery("select u from UserEntity u where u.realm = :realm", UserEntity.class);
+ query.setParameter("realm", realm);
+ List<UserEntity> results = query.getResultList();
+ List<User> users = new ArrayList<User>();
+ for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity));
+ return users;
+ }
+
+ @Override
+ public List<User> searchForUser(String search, String realm) {
+ TypedQuery<UserEntity> query = em.createQuery("select u from UserEntity u where u.realm = :realm and ( lower(u.username) like :search or lower(concat(u.firstName, ' ', u.lastName)) like :search or u.email like :search )", UserEntity.class);
+ query.setParameter("realm", realm);
+ query.setParameter("search", "%" + search.toLowerCase() + "%");
+ List<UserEntity> results = query.getResultList();
+ List<User> users = new ArrayList<User>();
+ for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity));
+ return users;
+ }
+
+ @Override
+ public List<User> searchForUserByAttributes(Map<String, String> attributes, String realm) {
+ StringBuilder builder = new StringBuilder("select u from UserEntity u");
+ boolean first = true;
+ for (Map.Entry<String, String> entry : attributes.entrySet()) {
+ String attribute = null;
+ if (entry.getKey().equals(User.USERNAME)) {
+ attribute = "lower(username)";
+ } else if (entry.getKey().equalsIgnoreCase(User.FIRST_NAME)) {
+ attribute = "lower(firstName)";
+ } else if (entry.getKey().equalsIgnoreCase(User.LAST_NAME)) {
+ attribute = "lower(lastName)";
+ } else if (entry.getKey().equalsIgnoreCase(User.EMAIL)) {
+ attribute = "lower(email)";
+ }
+ if (attribute == null) continue;
+ if (first) {
+ first = false;
+ builder.append(" where realm = :realm");
+ } else {
+ builder.append(" and ");
+ }
+ builder.append(attribute).append(" like '%").append(entry.getValue().toLowerCase()).append("%'");
+ }
+ String q = builder.toString();
+ TypedQuery<UserEntity> query = em.createQuery(q, UserEntity.class);
+ query.setParameter("realm", realm);
+ List<UserEntity> results = query.getResultList();
+ List<User> users = new ArrayList<User>();
+ for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity));
+ return users;
+ }
+
+ @Override
+ public boolean supports(Feature feature) {
+ return (feature == Feature.READ_CREDENTIALS || feature == Feature.UPDATE_CREDENTIALS);
+ }
+
+ @Override
+ public boolean verifyCredentials(User user, Credentials... credentials) {
+ return false;
+ }
+
+ @Override
+ public void onRealmRemoved(String realm) {
+ TypedQuery<UserEntity> query = em.createQuery("select u from UserEntity u where u.realm = :realm", UserEntity.class);
+ query.setParameter("realm", realm);
+ for (UserEntity u : query.getResultList()) {
+ em.remove(u);
+ }
+ }
+
+ @Override
+ public void onRoleRemoved(String role) {
+ em.createQuery("delete from RoleEntity r where r.role = :role").setParameter("role", role).executeUpdate();
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaUserProviderFactory.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaUserProviderFactory.java
new file mode 100755
index 0000000..64e8b06
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/JpaUserProviderFactory.java
@@ -0,0 +1,44 @@
+package org.keycloak.models.users.jpa;
+
+import org.keycloak.Config;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.users.Feature;
+import org.keycloak.models.users.UserProvider;
+import org.keycloak.models.users.UserProviderFactory;
+import org.keycloak.util.JpaUtils;
+
+import javax.persistence.EntityManagerFactory;
+import javax.persistence.Persistence;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class JpaUserProviderFactory implements UserProviderFactory {
+
+ public static final String ID = "jpa";
+
+ protected EntityManagerFactory emf;
+
+ @Override
+ public void init(Config.Scope config) {
+ String persistenceUnit = config.get("persistenceUnit", "jpa-keycloak-identity-store");
+ emf = Persistence.createEntityManagerFactory(persistenceUnit, JpaUtils.getHibernateProperties());
+ }
+
+ @Override
+ public String getId() {
+ return ID;
+ }
+
+ @Override
+ public UserProvider create(KeycloakSession session) {
+ return new JpaUserProvider(emf.createEntityManager());
+ }
+
+ @Override
+ public void close() {
+ emf.close();
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/PersistenceExceptionConverter.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/PersistenceExceptionConverter.java
new file mode 100644
index 0000000..ddd1c58
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/PersistenceExceptionConverter.java
@@ -0,0 +1,48 @@
+package org.keycloak.models.users.jpa;
+
+import org.hibernate.exception.ConstraintViolationException;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.ModelDuplicateException;
+
+import javax.persistence.EntityExistsException;
+import javax.persistence.EntityManager;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class PersistenceExceptionConverter implements InvocationHandler {
+
+ private EntityManager em;
+
+ public static EntityManager create(EntityManager em) {
+ return (EntityManager) Proxy.newProxyInstance(EntityManager.class.getClassLoader(), new Class[]{EntityManager.class}, new PersistenceExceptionConverter(em));
+ }
+
+ private PersistenceExceptionConverter(EntityManager em) {
+ this.em = em;
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ return method.invoke(em, args);
+ } catch (InvocationTargetException e) {
+ throw convert(e.getCause());
+ }
+ }
+
+ public static ModelException convert(Throwable t) {
+ if (t.getCause() != null && t.getCause() instanceof ConstraintViolationException) {
+ throw new ModelDuplicateException(t);
+ } if (t instanceof EntityExistsException) {
+ throw new ModelDuplicateException(t);
+ } else {
+ throw new ModelException(t);
+ }
+ }
+
+}
diff --git a/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/UserAdapter.java b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/UserAdapter.java
new file mode 100755
index 0000000..2851379
--- /dev/null
+++ b/model/users-jpa/src/main/java/org/keycloak/models/users/jpa/UserAdapter.java
@@ -0,0 +1,215 @@
+package org.keycloak.models.users.jpa;
+
+import org.keycloak.models.UserModel;
+import org.keycloak.models.users.Credentials;
+import org.keycloak.models.users.User;
+import org.keycloak.models.users.jpa.entities.UserAttributeEntity;
+import org.keycloak.models.users.jpa.entities.UserCredentialEntity;
+import org.keycloak.models.users.jpa.entities.UserRoleMappingEntity;
+import org.keycloak.models.users.jpa.entities.UserEntity;
+
+import javax.persistence.EntityManager;
+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;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class UserAdapter implements User {
+
+ protected UserEntity user;
+ protected EntityManager em;
+ protected String realm;
+
+ public UserAdapter(String realm, EntityManager em, UserEntity user) {
+ this.em = em;
+ this.user = user;
+ this.realm = realm;
+ }
+
+ public UserEntity getUser() {
+ return user;
+ }
+
+ @Override
+ public String getId() {
+ return user.getId();
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return user.isEnabled();
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ user.setEnabled(enabled);
+ }
+
+ @Override
+ public String getUsername() {
+ return user.getUsername();
+ }
+
+ @Override
+ public void setUsername(String username) {
+ user.setUsername(username);
+ }
+
+ @Override
+ public String getFirstName() {
+ return user.getFirstName();
+ }
+
+ @Override
+ public void setFirstName(String firstName) {
+ user.setFirstName(firstName);
+ }
+
+ @Override
+ public String getLastName() {
+ return user.getLastName();
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ user.setLastName(lastName);
+ }
+
+ @Override
+ public String getEmail() {
+ return user.getEmail();
+ }
+
+ @Override
+ public void setEmail(String email) {
+ user.setEmail(email);
+ }
+
+
+ @Override
+ public void setAttribute(String name, String value) {
+ List<UserAttributeEntity> attributes = user.getAttributes();
+ for (UserAttributeEntity a : user.getAttributes()) {
+ if (a.getName().equals(name)) {
+ a.setValue(value);
+ return;
+ }
+ }
+ attributes.add(new UserAttributeEntity(user, name, value));
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ for (UserAttributeEntity a : user.getAttributes()) {
+ if (a.getName().equals(name)) {
+ return a.getValue();
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+ Map<String, String> result = new HashMap<String, String>();
+ for (UserAttributeEntity a : user.getAttributes()) {
+ result.put(a.getName(), a.getValue());
+ }
+ return result;
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ Iterator<UserAttributeEntity> itr = user.getAttributes().iterator();
+ while(itr.hasNext()) {
+ if (itr.next().getName().equals(name)) {
+ itr.remove();
+ return;
+ }
+ }
+ }
+
+ private UserCredentialEntity getCredentialEntity(String credType) {
+ for (UserCredentialEntity entity : user.getCredentials()) {
+ if (entity.getType().equals(credType)) {
+ return entity;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public List<Credentials> getCredentials() {
+ List<Credentials> result = new ArrayList<Credentials>();
+ for (UserCredentialEntity entity : user.getCredentials()) {
+ result.add(new Credentials(entity.getType(), entity.getSalt(), entity.getValue(), entity.getHashIterations(), entity.getDevice()));
+ }
+ return result;
+ }
+
+ @Override
+ public void updateCredential(Credentials credentials) {
+ UserCredentialEntity entity = getCredentialEntity(credentials.getType());
+ if (entity == null) {
+ entity = new UserCredentialEntity(user, credentials.getType());
+ user.getCredentials().add(entity);
+ }
+
+ entity.setValue(credentials.getValue());
+ entity.setSalt(credentials.getSalt());
+ entity.setHashIterations(credentials.getHashIterations());
+ entity.setDevice(credentials.getDevice());
+ }
+
+ @Override
+ public void grantRole(String role) {
+ for (UserRoleMappingEntity r : user.getRoles()) {
+ if (r.getRole().equals(role)) {
+ return;
+ }
+ }
+
+ user.getRoles().add(new UserRoleMappingEntity(user, role));
+ }
+
+ @Override
+ public Set<String> getRoleMappings() {
+ Set<String> roles = new HashSet<String>();
+ for (UserRoleMappingEntity r : user.getRoles()) {
+ roles.add(r.getRole());
+ }
+ return roles;
+ }
+
+ @Override
+ public void deleteRoleMapping(String role) {
+ Iterator<UserRoleMappingEntity> itr = user.getRoles().iterator();
+ while (itr.hasNext()) {
+ if (itr.next().getRole().equals(role)) {
+ itr.remove();
+ return;
+ }
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || !(o instanceof UserModel)) return false;
+
+ UserModel that = (UserModel) o;
+ return that.getId().equals(getId());
+ }
+
+ @Override
+ public int hashCode() {
+ return getId().hashCode();
+ }
+
+}
diff --git a/model/users-jpa/src/main/resources/META-INF/services/org.keycloak.models.users.UserProviderFactory b/model/users-jpa/src/main/resources/META-INF/services/org.keycloak.models.users.UserProviderFactory
new file mode 100644
index 0000000..43ec95f
--- /dev/null
+++ b/model/users-jpa/src/main/resources/META-INF/services/org.keycloak.models.users.UserProviderFactory
@@ -0,0 +1 @@
+org.keycloak.models.users.jpa.JpaUserProviderFactory
\ No newline at end of file
diff --git a/model/users-jpa/src/test/java/org/keycloak/models/users/jpa/JpaUserProviderTest.java b/model/users-jpa/src/test/java/org/keycloak/models/users/jpa/JpaUserProviderTest.java
new file mode 100644
index 0000000..0d14739
--- /dev/null
+++ b/model/users-jpa/src/test/java/org/keycloak/models/users/jpa/JpaUserProviderTest.java
@@ -0,0 +1,15 @@
+package org.keycloak.models.users.jpa;
+
+import org.keycloak.model.test.AbstractUserProviderTest;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class JpaUserProviderTest extends AbstractUserProviderTest {
+
+ @Override
+ protected String getProviderId() {
+ return JpaUserProviderFactory.ID;
+ }
+
+}
diff --git a/model/users-jpa/src/test/resources/META-INF/persistence.xml b/model/users-jpa/src/test/resources/META-INF/persistence.xml
new file mode 100755
index 0000000..208c895
--- /dev/null
+++ b/model/users-jpa/src/test/resources/META-INF/persistence.xml
@@ -0,0 +1,25 @@
+<persistence xmlns="http://java.sun.com/xml/ns/persistence"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
+ version="1.0">
+ <persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
+ <provider>org.hibernate.ejb.HibernatePersistence</provider>
+
+ <class>org.keycloak.models.users.jpa.entities.UserAttributeEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserCredentialEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserRoleMappingEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserEntity</class>
+
+ <exclude-unlisted-classes>true</exclude-unlisted-classes>
+
+ <properties>
+ <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
+ <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
+ <property name="hibernate.connection.username" value="sa"/>
+ <property name="hibernate.connection.password" value=""/>
+ <property name="hibernate.hbm2ddl.auto" value="create-drop" />
+ <property name="hibernate.show_sql" value="false" />
+ <property name="hibernate.format_sql" value="true" />
+ </properties>
+ </persistence-unit>
+</persistence>
server/pom.xml 22(+21 -1)
diff --git a/server/pom.xml b/server/pom.xml
index 2d8e61d..d0318f4 100755
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -49,7 +49,27 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-model-jpa</artifactId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-realms-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-users-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-mem</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
diff --git a/server/src/main/resources/META-INF/keycloak-server.json b/server/src/main/resources/META-INF/keycloak-server.json
index 7e3f246..6133aa4 100755
--- a/server/src/main/resources/META-INF/keycloak-server.json
+++ b/server/src/main/resources/META-INF/keycloak-server.json
@@ -11,13 +11,25 @@
},
"model": {
- "provider": "jpa"
+ "provider": "hybrid"
},
"modelCache": {
"provider": "${keycloak.model.cache.provider:}"
},
+ "modelRealms": {
+ "provider": "jpa"
+ },
+
+ "modelUsers": {
+ "provider": "jpa"
+ },
+
+ "modelSessions": {
+ "provider": "mem"
+ },
+
"timer": {
"provider": "basic"
},
diff --git a/server/src/main/resources/META-INF/persistence.xml b/server/src/main/resources/META-INF/persistence.xml
index a704014..bba0831 100755
--- a/server/src/main/resources/META-INF/persistence.xml
+++ b/server/src/main/resources/META-INF/persistence.xml
@@ -4,21 +4,23 @@
version="1.0">
<persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
- <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
- <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
- <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
- <class>org.keycloak.models.jpa.entities.RealmEntity</class>
- <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
- <class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
- <class>org.keycloak.models.jpa.entities.RoleEntity</class>
- <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
- <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
- <class>org.keycloak.models.jpa.entities.UserEntity</class>
- <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
- <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
- <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
- <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
- <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
+
+ <class>org.keycloak.models.realms.jpa.entities.ApplicationEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.OAuthClientEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.RealmEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.RequiredCredentialEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.AuthenticationProviderEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.RoleEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.ScopeMappingEntity</class>
+
+ <class>org.keycloak.models.users.jpa.entities.UserAttributeEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserCredentialEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserRoleMappingEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserEntity</class>
+
+ <class>org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
@@ -26,7 +28,7 @@
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
-
+
<persistence-unit name="jpa-keycloak-audit-store" transaction-type="RESOURCE_LOCAL">
<jta-data-source>java:jboss/datasources/ExampleDS</jta-data-source>
<class>org.keycloak.audit.jpa.EventEntity</class>
@@ -37,5 +39,4 @@
<property name="hibernate.hbm2ddl.auto" value="update" />
</properties>
</persistence-unit>
-
</persistence>
diff --git a/services/src/main/java/org/keycloak/services/resources/AccountService.java b/services/src/main/java/org/keycloak/services/resources/AccountService.java
index 1641757..2bf2dbd 100755
--- a/services/src/main/java/org/keycloak/services/resources/AccountService.java
+++ b/services/src/main/java/org/keycloak/services/resources/AccountService.java
@@ -48,6 +48,7 @@ import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.TimeBasedOTP;
import org.keycloak.representations.idm.CredentialRepresentation;
+import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.services.ForbiddenException;
import org.keycloak.services.managers.AppAuthManager;
import org.keycloak.services.managers.Auth;
@@ -220,7 +221,15 @@ public class AccountService {
} else if (types.contains(MediaType.APPLICATION_JSON_TYPE)) {
requireOneOf(AccountRoles.MANAGE_ACCOUNT, AccountRoles.VIEW_PROFILE);
- return Cors.add(request, Response.ok(ModelToRepresentation.toRepresentation(auth.getUser()))).auth().allowedOrigins(auth.getToken()).build();
+ UserRepresentation rep = ModelToRepresentation.toRepresentation(auth.getUser());
+ Iterator<String> itr = rep.getAttributes().keySet().iterator();
+ while (itr.hasNext()) {
+ if (itr.next().startsWith("keycloak.")) {
+ itr.remove();
+ }
+ }
+
+ return Cors.add(request, Response.ok(rep)).auth().allowedOrigins(auth.getToken()).build();
} else {
return Response.notAcceptable(Variant.VariantListBuilder.newInstance().mediaTypes(MediaType.TEXT_HTML_TYPE, MediaType.APPLICATION_JSON_TYPE).build()).build();
}
testsuite/integration/pom.xml 22(+21 -1)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 052f6b1..50466c8 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -122,7 +122,27 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
- <artifactId>keycloak-model-jpa</artifactId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-realms-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-users-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-mem</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-jpa</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
diff --git a/testsuite/integration/src/main/resources/META-INF/keycloak-server.json b/testsuite/integration/src/main/resources/META-INF/keycloak-server.json
index 9204d6f..c39a794 100755
--- a/testsuite/integration/src/main/resources/META-INF/keycloak-server.json
+++ b/testsuite/integration/src/main/resources/META-INF/keycloak-server.json
@@ -1,58 +1,70 @@
-{
- "admin": {
- "realm": "master"
- },
-
- "audit": {
- "provider": "${keycloak.audit.provider,keycloak.model.provider:jpa}",
- "mongo": {
- "host": "${keycloak.audit.mongo.host:127.0.0.1}",
- "port": "${keycloak.audit.mongo.port:27017}",
- "db": "${keycloak.audit.mongo.db:keycloak-audit}",
- "clearOnStartup": "${keycloak.audit.mongo.clearOnStartup:false}"
- }
- },
-
- "model": {
- "provider": "${keycloak.model.provider:jpa}",
- "mongo": {
- "host": "${keycloak.model.mongo.host:127.0.0.1}",
- "port": "${keycloak.model.mongo.port:27017}",
- "db": "${keycloak.model.mongo.db:keycloak}",
- "clearOnStartup": "${keycloak.model.mongo.clearOnStartup:false}"
- }
- },
-
- "modelCache": {
- "provider": "${keycloak.model.cache.provider:simple}"
- },
-
- "timer": {
- "provider": "basic"
- },
-
- "theme": {
- "default": "keycloak",
- "staticMaxAge": 2592000,
- "cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
- "folder": {
- "dir": "${keycloak.theme.dir}"
- }
- },
-
- "login": {
- "provider": "freemarker"
- },
-
- "account": {
- "provider": "freemarker"
- },
-
- "email": {
- "provider": "freemarker"
- },
-
- "scheduled": {
- "interval": 900
- }
+{
+ "admin": {
+ "realm": "master"
+ },
+
+ "audit": {
+ "provider": "${keycloak.audit.provider,keycloak.model.provider:jpa}",
+ "mongo": {
+ "host": "${keycloak.audit.mongo.host:127.0.0.1}",
+ "port": "${keycloak.audit.mongo.port:27017}",
+ "db": "${keycloak.audit.mongo.db:keycloak-audit}",
+ "clearOnStartup": "${keycloak.audit.mongo.clearOnStartup:false}"
+ }
+ },
+
+ "model": {
+ "provider": "${keycloak.model.provider:hybrid}",
+ "mongo": {
+ "host": "${keycloak.model.mongo.host:127.0.0.1}",
+ "port": "${keycloak.model.mongo.port:27017}",
+ "db": "${keycloak.model.mongo.db:keycloak}",
+ "clearOnStartup": "${keycloak.model.mongo.clearOnStartup:false}"
+ }
+ },
+
+ "modelCache": {
+ "provider": "${keycloak.model.cache.provider:simple}"
+ },
+
+ "modelRealms": {
+ "provider": "jpa"
+ },
+
+ "modelUsers": {
+ "provider": "jpa"
+ },
+
+ "modelSessions": {
+ "provider": "mem"
+ },
+
+ "timer": {
+ "provider": "basic"
+ },
+
+ "theme": {
+ "default": "keycloak",
+ "staticMaxAge": 2592000,
+ "cacheTemplates": "${keycloak.theme.cacheTemplates:true}",
+ "folder": {
+ "dir": "${keycloak.theme.dir}"
+ }
+ },
+
+ "login": {
+ "provider": "freemarker"
+ },
+
+ "account": {
+ "provider": "freemarker"
+ },
+
+ "email": {
+ "provider": "freemarker"
+ },
+
+ "scheduled": {
+ "interval": 900
+ }
}
\ No newline at end of file
diff --git a/testsuite/integration/src/main/resources/META-INF/persistence.xml b/testsuite/integration/src/main/resources/META-INF/persistence.xml
index 39dd390..5a261d7 100755
--- a/testsuite/integration/src/main/resources/META-INF/persistence.xml
+++ b/testsuite/integration/src/main/resources/META-INF/persistence.xml
@@ -5,21 +5,22 @@
<persistence-unit name="jpa-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
- <class>org.keycloak.models.jpa.entities.ApplicationEntity</class>
- <class>org.keycloak.models.jpa.entities.CredentialEntity</class>
- <class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
- <class>org.keycloak.models.jpa.entities.RealmEntity</class>
- <class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
- <class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
- <class>org.keycloak.models.jpa.entities.RoleEntity</class>
- <class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
- <class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
- <class>org.keycloak.models.jpa.entities.UserEntity</class>
- <class>org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity</class>
- <class>org.keycloak.models.jpa.entities.UserSessionEntity</class>
- <class>org.keycloak.models.jpa.entities.UsernameLoginFailureEntity</class>
- <class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
- <class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.ApplicationEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.OAuthClientEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.RealmEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.RequiredCredentialEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.AuthenticationProviderEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.RoleEntity</class>
+ <class>org.keycloak.models.realms.jpa.entities.ScopeMappingEntity</class>
+
+ <class>org.keycloak.models.users.jpa.entities.UserAttributeEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserCredentialEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserRoleMappingEntity</class>
+ <class>org.keycloak.models.users.jpa.entities.UserEntity</class>
+
+ <class>org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity</class>
+ <class>org.keycloak.models.sessions.jpa.entities.UserSessionEntity</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
@@ -51,38 +52,4 @@
<property name="hibernate.format_sql" value="true" />
</properties>
</persistence-unit>
-
- <!--
- <persistence-unit name="picketlink-keycloak-identity-store" transaction-type="RESOURCE_LOCAL">
- <provider>org.hibernate.ejb.HibernatePersistence</provider>
-
- <class>org.picketlink.idm.jpa.model.sample.simple.AttributedTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.AccountTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.RoleTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.GroupTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.IdentityTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.RelationshipIdentityTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.PartitionTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.PasswordCredentialTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.DigestCredentialTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.X509CredentialTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.OTPCredentialTypeEntity</class>
- <class>org.picketlink.idm.jpa.model.sample.simple.AttributeTypeEntity</class>
- <class>org.keycloak.models.picketlink.mappings.RealmEntity</class>
- <class>org.keycloak.models.picketlink.mappings.ApplicationEntity</class>
-
- <exclude-unlisted-classes>true</exclude-unlisted-classes>
-
- <properties>
- <property name="hibernate.connection.url" value="jdbc:h2:mem:test"/>
- <property name="hibernate.connection.driver_class" value="org.h2.Driver"/>
- <property name="hibernate.connection.username" value="sa"/>
- <property name="hibernate.connection.password" value=""/>
- <property name="hibernate.hbm2ddl.auto" value="create-drop" />
- <property name="hibernate.show_sql" value="false" />
- <property name="hibernate.format_sql" value="true" />
- </properties>
- </persistence-unit>
- -->
</persistence>
testsuite/performance/pom.xml 25(+25 -0)
diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml
index 1ba9c0f..201abf3 100755
--- a/testsuite/performance/pom.xml
+++ b/testsuite/performance/pom.xml
@@ -42,6 +42,31 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-realms-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-users-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-mem</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-model-mongo</artifactId>
<version>${project.version}</version>
</dependency>
testsuite/tools/pom.xml 20(+20 -0)
diff --git a/testsuite/tools/pom.xml b/testsuite/tools/pom.xml
index c630624..31d1492 100755
--- a/testsuite/tools/pom.xml
+++ b/testsuite/tools/pom.xml
@@ -68,6 +68,26 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-hybrid</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-realms-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-users-jpa</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
+ <artifactId>keycloak-model-sessions-mem</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-audit-api</artifactId>
<version>${project.version}</version>
</dependency>