diff --git a/model/hybrid/pom.xml b/model/hybrid/pom.xml new file mode 100755 index 0000000000..344fef31c7 --- /dev/null +++ b/model/hybrid/pom.xml @@ -0,0 +1,30 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-hybrid + Keycloak Model Hybrid + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + provided + + + 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 0000000000..57ff498158 --- /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 Stian Thorgersen + */ +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 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 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 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 0000000000..a35b662c12 --- /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 Stian Thorgersen + */ +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 getWebOrigins() { + return client.getWebOrigins(); + } + + @Override + public void setWebOrigins(Set 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 getRedirectUris() { + return client.getRedirectUris(); + } + + @Override + public void setRedirectUris(Set 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 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 getRealmScopeMappings() { + return provider.mappings().wrap(client.getRealmScopeMappings()); + } + + @Override + public boolean hasScope(RoleModel role) { + Set 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 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 0000000000..b6a54eaf09 --- /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 Stian Thorgersen + */ +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 0000000000..0a3d7785eb --- /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 Stian Thorgersen + */ +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 getUsers(RealmModel realm) { + return mappings.wrapUsers(realm, users().getUsers(realm.getId())); + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return mappings.wrapUsers(realm, users().searchForUser(search, realm.getId())); + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm) { + return mappings.wrapUsers(realm, users().searchForUserByAttributes(attributes, realm.getId())); + } + + @Override + public Set getSocialLinks(UserModel user, RealmModel realm) { + Set links = new HashSet(); + + for (Map.Entry 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 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 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 getUserSessions(UserModel user, RealmModel realm) { + return mappings.wrapSessions(realm, sessions().getUserSessionsByUser(user.getId(), realm.getId())); + } + + @Override + public Set 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 0000000000..314ea96a28 --- /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 Stian Thorgersen + */ +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 0000000000..39972aeac4 --- /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 Stian Thorgersen + */ +public class Mappings { + + private final HybridModelProvider provider; + private Map mappings = new HashMap(); + + 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 wrap(List realms) { + List adapters = new LinkedList(); + 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 wrap(Set roles) { + Set adapters = new HashSet(); + 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 wrapApps(List applications) { + List adapters = new LinkedList(); + for (Application application : applications) { + adapters.add(wrap(application)); + } + return adapters; + } + + public Map wrap(Map applications) { + Map adapters = new HashMap(); + for (Map.Entry 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 wrapClients(List clients) { + List adapters = new LinkedList(); + 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 wrapUsers(RealmModel realm, List users) { + List adapters = new LinkedList(); + 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 wrapSessions(RealmModel realm, List sessions) { + List adapters = new LinkedList(); + for (Session session : sessions) { + adapters.add(wrap(realm, session)); + } + return adapters; + } + + public Set wrapSessions(RealmModel realm, Set sessions) { + Set adapters = new HashSet(); + 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 wrapLoginFailures(List loginFailures) { + List adapters = new LinkedList(); + 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 0000000000..308e819ed8 --- /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 Stian Thorgersen + */ +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 0000000000..2f3e0a2b51 --- /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 Stian Thorgersen + */ +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 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 initialRoles = new HashSet(); + + 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 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 getApplicationNameMap() { + return provider.mappings().wrap(realm.getApplicationNameMap()); + } + + @Override + public List 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 creds) { + realm.updateRequiredCredentials(creds); + } + + @Override + public UserModel getUserBySocialLink(SocialLinkModel socialLink) { + return provider.getUserBySocialLink(socialLink, this); + } + + @Override + public Set 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 getAllUserLoginFailures() { + return provider.getAllUserLoginFailures(this); + } + + @Override + public List getUsers() { + return provider.getUsers(this); + } + + @Override + public List searchForUser(String search) { + return provider.searchForUser(search, this); + } + + @Override + public List searchForUserByAttributes(Map 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 getOAuthClients() { + return provider.mappings().wrapClients(realm.getOAuthClients()); + } + + @Override + public Map getSmtpConfig() { + return realm.getSmtpConfig(); + } + + @Override + public void setSmtpConfig(Map smtpConfig) { + realm.setSmtpConfig(smtpConfig); + } + + @Override + public Map getSocialConfig() { + return realm.getSocialConfig(); + } + + @Override + public void setSocialConfig(Map socialConfig) { + realm.setSocialConfig(socialConfig); + } + + @Override + public Map getLdapServerConfig() { + return realm.getLdapServerConfig(); + } + + @Override + public void setLdapServerConfig(Map ldapServerConfig) { + realm.setLdapServerConfig(ldapServerConfig); + } + + @Override + public List getAuthenticationProviders() { + return realm.getAuthenticationProviders(); + } + + @Override + public void setAuthenticationProviders(List 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 getAuditListeners() { + return realm.getAuditListeners(); + } + + @Override + public void setAuditListeners(Set 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 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 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 0000000000..c6e79efa10 --- /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 Stian Thorgersen + */ +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 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 visited = new HashSet(); + 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 0000000000..e082fc6ff2 --- /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 Stian Thorgersen + */ +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 getAttributes() { + return user.getAttributes(); + } + + @Override + public Set getRequiredActions() { + String value = user.getAttribute(Attributes.REQUIRED_ACTIONS); + if (value == null) { + return Collections.emptySet(); + } + + Set actions = new HashSet(); + 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 actions; + if (user.getAttribute(Attributes.REQUIRED_ACTIONS) == null) { + actions = new HashSet(); + } else { + actions = getRequiredActions(); + } + + if (!actions.contains(action)) { + actions.add(action); + user.setAttribute(Attributes.REQUIRED_ACTIONS, actions.toString()); + } + } + + @Override + public void removeRequiredAction(RequiredAction action) { + Set 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 getCredentialsDirectly() { + if (provider.users().supports(Feature.READ_CREDENTIALS)) { + List models = new LinkedList(); + 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 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> 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 getRealmRoleMappings() { + Set roles = new HashSet(); + for (RoleModel r : getRoleMappings()) { + if (r.getContainer() instanceof RealmModel) { + roles.add(r); + } + } + return roles; + } + + @Override + public Set getApplicationRoleMappings(ApplicationModel app) { + Set roles = new HashSet(); + 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 getRoleMappings() { + Set roles = new HashSet(); + 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/UserSessionAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UserSessionAdapter.java new file mode 100644 index 0000000000..8b5a0d9f93 --- /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 Stian Thorgersen + */ +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 getClientAssociations() { + List clients = new LinkedList(); + 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/hybrid/UsernameLoginFailureAdapter.java b/model/hybrid/src/main/java/org/keycloak/models/hybrid/UsernameLoginFailureAdapter.java new file mode 100644 index 0000000000..35b9539a7c --- /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 Stian Thorgersen + */ +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/realms/Application.java b/model/hybrid/src/main/java/org/keycloak/models/realms/Application.java new file mode 100755 index 0000000000..bf6afb0c16 --- /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 Bill Burke + * @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 getDefaultRoles(); + + void addDefaultRole(String name); + + void updateDefaultRoles(String[] defaultRoles); + + Set 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 0000000000..f7d6a6a374 --- /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 Bill Burke + * @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 getWebOrigins(); + + void setWebOrigins(Set webOrigins); + + void addWebOrigin(String webOrigin); + + void removeWebOrigin(String webOrigin); + + Set getRedirectUris(); + + void setRedirectUris(Set 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 getScopeMappings(); + void addScopeMapping(Role role); + void deleteScopeMapping(Role role); + Set 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 0000000000..74f4cfdb11 --- /dev/null +++ b/model/hybrid/src/main/java/org/keycloak/models/realms/OAuthClient.java @@ -0,0 +1,11 @@ +package org.keycloak.models.realms; + +/** + * @author Bill Burke + * @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 0000000000..5f1d6e8953 --- /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 Bill Burke + * @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 getRequiredCredentials(); + + void addRequiredCredential(String cred); + + PasswordPolicy getPasswordPolicy(); + + void setPasswordPolicy(PasswordPolicy policy); + + List getDefaultRoles(); + + void addDefaultRole(String name); + + void updateDefaultRoles(String[] defaultRoles); + + Client findClient(String clientId); + + Map getApplicationNameMap(); + + List getApplications(); + + Application addApplication(String id, String name); + + boolean removeApplication(Application application); + + Application getApplicationByName(String name); + + void updateRequiredCredentials(Set 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 getOAuthClients(); + + Map getSmtpConfig(); + + void setSmtpConfig(Map smtpConfig); + + Map getSocialConfig(); + + void setSocialConfig(Map socialConfig); + + Map getLdapServerConfig(); + + void setLdapServerConfig(Map ldapServerConfig); + + List getAuthenticationProviders(); + + void setAuthenticationProviders(List 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 getAuditListeners(); + + void setAuditListeners(Set 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 0000000000..b762d67ca4 --- /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 Stian Thorgersen + */ +public interface RealmProvider extends Provider { + + Realm createRealm(String name); + Realm createRealm(String id, String name); + + Realm getRealm(String id); + Realm getRealmByName(String name); + List 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 0000000000..fb4dea3120 --- /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 Stian Thorgersen + */ +public interface RealmProviderFactory extends ProviderFactory { +} 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 0000000000..9ec584f0b9 --- /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 Stian Thorgersen + */ +public class RealmSpi implements Spi { + + @Override + public String getName() { + return "modelRealms"; + } + + @Override + public Class getProviderClass() { + return RealmProvider.class; + } + + @Override + public Class 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 0000000000..431d1b6bac --- /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 Bill Burke + * @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 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 0000000000..5d4c02cbd0 --- /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 Bill Burke + * @version $Revision: 1 $ + */ +public interface RoleContainer { + + Role getRole(String name); + + Set 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 0000000000..f0e7b24c6f --- /dev/null +++ b/model/hybrid/src/main/java/org/keycloak/models/sessions/LoginFailure.java @@ -0,0 +1,29 @@ +package org.keycloak.models.sessions; + +/** + * @author Bill Burke + * @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 0000000000..722895d7a1 --- /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 Stian Thorgersen + */ +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 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 0000000000..baf0396afd --- /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 Stian Thorgersen + */ +public interface SessionProvider extends Provider { + + LoginFailure getUserLoginFailure(String username, String realm); + + LoginFailure addUserLoginFailure(String username, String realm); + + List getAllUserLoginFailures(String realm); + + Session createUserSession(String realm, String id, String user, String ipAddress); + + Session getUserSession(String id, String realm); + + List getUserSessionsByUser(String user, String realm); + + Set 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 0000000000..08e20511f2 --- /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 Stian Thorgersen + */ +public interface SessionProviderFactory extends ProviderFactory { +} 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 0000000000..cf6c7fc68e --- /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 Stian Thorgersen + */ +public class SessionSpi implements Spi { + + @Override + public String getName() { + return "modelSessions"; + } + + @Override + public Class getProviderClass() { + return SessionProvider.class; + } + + @Override + public Class 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 0000000000..c341ab09ec --- /dev/null +++ b/model/hybrid/src/main/java/org/keycloak/models/users/Attributes.java @@ -0,0 +1,12 @@ +package org.keycloak.models.users; + +/** + * @author Stian Thorgersen + */ +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 0000000000..57368bcc53 --- /dev/null +++ b/model/hybrid/src/main/java/org/keycloak/models/users/Credentials.java @@ -0,0 +1,72 @@ +package org.keycloak.models.users; + +/** + * @author Marek Posolda + */ +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 0000000000..395fb03b77 --- /dev/null +++ b/model/hybrid/src/main/java/org/keycloak/models/users/Feature.java @@ -0,0 +1,12 @@ +package org.keycloak.models.users; + +/** + * @author Stian Thorgersen + */ +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 0000000000..90f288003f --- /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 Bill Burke + * @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 getAttributes(); + + void setAttribute(String name, String value); + + void removeAttribute(String name); + + List getCredentials(); + + void updateCredential(Credentials cred); + + Set 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 0000000000..de53736507 --- /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 Stian Thorgersen + */ +public interface UserProvider extends Provider { + + KeycloakTransaction getTransaction(); + + User addUser(String id, String username, Set 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 getUsers(String realm); + List searchForUser(String search, String realm); + List searchForUserByAttributes(Map 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 0000000000..e2f7c46b4f --- /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 Stian Thorgersen + */ +public interface UserProviderFactory extends ProviderFactory { +} 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 0000000000..e3d8e232bd --- /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 Stian Thorgersen + */ +public class UserSpi implements Spi { + + @Override + public String getName() { + return "modelUsers"; + } + + @Override + public Class getProviderClass() { + return UserProvider.class; + } + + @Override + public Class 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 0000000000..4fdf8537c9 --- /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 0000000000..6f3050ea7f --- /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 diff --git a/model/pom.xml b/model/pom.xml index 850a21f322..50515477f6 100755 --- a/model/pom.xml +++ b/model/pom.xml @@ -31,5 +31,12 @@ jpa mongo tests + + hybrid + realms-jpa + users-jpa + sessions-mem + sessions-jpa + tests-hybrid diff --git a/model/realms-jpa/pom.xml b/model/realms-jpa/pom.xml new file mode 100755 index 0000000000..09413e8484 --- /dev/null +++ b/model/realms-jpa/pom.xml @@ -0,0 +1,45 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-realms-jpa + Keycloak Model Realms JPA + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + + + org.keycloak + keycloak-model-hybrid + ${project.version} + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + provided + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymanager.version} + provided + + + 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 0000000000..e44c7b1cee --- /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 Bill Burke + * @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 query = em.createNamedQuery("getAppRoleByName", RoleEntity.class); + query.setParameter("name", name); + query.setParameter("application", entity); + List 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 getRoles() { + Set list = new HashSet(); + Collection roles = applicationEntity.getRoles(); + if (roles == null) return list; + for (RoleEntity entity : roles) { + list.add(new RoleAdapter(provider, em, entity)); + } + return list; + } + + @Override + public Set getApplicationScopeMappings(Client client) { + Set roleMappings = client.getScopeMappings(); + + Set appRoles = new HashSet(); + 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 getDefaultRoles() { + Collection entities = applicationEntity.getDefaultRoles(); + List roles = new ArrayList(); + 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 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 entities = applicationEntity.getDefaultRoles(); + Set already = new HashSet(); + List remove = new ArrayList(); + 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 0000000000..660646e213 --- /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 Bill Burke + * @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 getWebOrigins() { + Set result = new HashSet(); + result.addAll(entity.getWebOrigins()); + return result; + } + + + + @Override + public void setWebOrigins(Set 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 getRedirectUris() { + Set result = new HashSet(); + result.addAll(entity.getRedirectUris()); + return result; + } + + @Override + public void setRedirectUris(Set 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 getRealmScopeMappings() { + Set roleMappings = getScopeMappings(); + + Set appRoles = new HashSet(); + 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 getScopeMappings() { + TypedQuery query = em.createNamedQuery("clientScopeMappingIds", String.class); + query.setParameter("client", getEntity()); + List ids = query.getResultList(); + Set roles = new HashSet(); + 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 query = getRealmScopeMappingQuery((RoleAdapter) role); + List results = query.getResultList(); + if (results.size() == 0) return; + for (ScopeMappingEntity entity : results) { + em.remove(entity); + } + } + + protected TypedQuery getRealmScopeMappingQuery(RoleAdapter role) { + TypedQuery 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/JpaKeycloakTransaction.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/JpaKeycloakTransaction.java new file mode 100755 index 0000000000..fd417141f2 --- /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 Bill Burke + * @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 0000000000..9f8f043d83 --- /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 Bill Burke + * @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 getRealms() { + TypedQuery query = em.createNamedQuery("getAllRealms", RealmEntity.class); + List entities = query.getResultList(); + List realms = new ArrayList(); + for (RealmEntity entity : entities) { + realms.add(new RealmAdapter(this, em, entity)); + } + return realms; + } + + @Override + public Realm getRealmByName(String name) { + TypedQuery query = em.createNamedQuery("getRealmByName", RealmEntity.class); + query.setParameter("name", name); + List 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 0000000000..e181b8e07e --- /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 Bill Burke + * @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 0000000000..39bf489648 --- /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 Bill Burke + * @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 0000000000..f479939eb6 --- /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 Stian Thorgersen + */ +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 0000000000..578fa65b82 --- /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 Bill Burke + * @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 creds) { + Collection relationships = realm.getRequiredCredentials(); + if (relationships == null) relationships = new ArrayList(); + + Set already = new HashSet(); + List remove = new ArrayList(); + 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 getRequiredCredentials() { + List requiredCredentialModels = new ArrayList(); + Collection 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 getDefaultRoles() { + Collection entities = realm.getDefaultRoles(); + List roles = new ArrayList(); + 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 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 entities = realm.getDefaultRoles(); + Set already = new HashSet(); + List remove = new ArrayList(); + 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 getApplicationNameMap() { + Map map = new HashMap(); + 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 getApplications() { + List list = new ArrayList(); + 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 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 query = em.createNamedQuery("findOAuthClientByName", OAuthClientEntity.class); + query.setParameter("name", name); + query.setParameter("realm", realm); + List entities = query.getResultList(); + if (entities.size() == 0) return null; + return new OAuthClientAdapter(provider, entities.get(0), em); + } + + @Override + public List getOAuthClients() { + TypedQuery query = em.createNamedQuery("findOAuthClientByRealm", OAuthClientEntity.class); + query.setParameter("realm", realm); + List entities = query.getResultList(); + List list = new ArrayList(); + for (OAuthClientEntity entity : entities) list.add(new OAuthClientAdapter(provider, entity, em)); + return list; + } + + @Override + public Map getSmtpConfig() { + return realm.getSmtpConfig(); + } + + @Override + public void setSmtpConfig(Map smtpConfig) { + realm.setSmtpConfig(smtpConfig); + em.flush(); + } + + @Override + public Map getSocialConfig() { + return realm.getSocialConfig(); + } + + @Override + public void setSocialConfig(Map socialConfig) { + realm.setSocialConfig(socialConfig); + em.flush(); + } + + @Override + public Map getLdapServerConfig() { + return realm.getLdapServerConfig(); + } + + @Override + public void setLdapServerConfig(Map ldapServerConfig) { + realm.setLdapServerConfig(ldapServerConfig); + em.flush(); + } + + @Override + public List getAuthenticationProviders() { + List entities = realm.getAuthenticationProviders(); + List copy = new ArrayList(); + for (AuthenticationProviderEntity entity : entities) { + copy.add(entity); + + } + Collections.sort(copy, new Comparator() { + + @Override + public int compare(AuthenticationProviderEntity o1, AuthenticationProviderEntity o2) { + return o1.getPriority() - o2.getPriority(); + } + + }); + List result = new ArrayList(); + for (AuthenticationProviderEntity entity : copy) { + result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig())); + } + + return result; + } + + @Override + public void setAuthenticationProviders(List authenticationProviders) { + List newEntities = new ArrayList(); + 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 existing = realm.getAuthenticationProviders(); + Collection copy = new ArrayList(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 query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class); + query.setParameter("name", name); + query.setParameter("realm", realm); + List 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 getRoles() { + Set list = new HashSet(); + Collection 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 getAuditListeners() { + return realm.getAuditListeners(); + } + + @Override + public void setAuditListeners(Set 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 0000000000..0063794d19 --- /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 Bill Burke + * @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 it = getRole().getCompositeRoles().iterator(); + while (it.hasNext()) { + if (it.next().equals(entity)) it.remove(); + } + } + + @Override + public Set getComposites() { + Set set = new HashSet(); + + 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/entities/ApplicationEntity.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/entities/ApplicationEntity.java new file mode 100755 index 0000000000..6093fbcb59 --- /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 Bill Burke + * @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 roles = new ArrayList(); + + @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true) + @JoinTable(name="ApplicationDefaultRoles") + Collection defaultRoles = new ArrayList(); + + 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 getRoles() { + return roles; + } + + public void setRoles(Collection roles) { + this.roles = roles; + } + + public Collection getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(Collection 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 0000000000..261a10b0a7 --- /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 Marek Posolda + */ +@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 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 getConfig() { + return config; + } + + public void setConfig(Map 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 0000000000..05eb5e0bd5 --- /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 Bill Burke + * @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 webOrigins = new HashSet(); + @ElementCollection + @CollectionTable + protected Set redirectUris = new HashSet(); + + 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 getWebOrigins() { + return webOrigins; + } + + public void setWebOrigins(Set webOrigins) { + this.webOrigins = webOrigins; + } + + public Set getRedirectUris() { + return redirectUris; + } + + public void setRedirectUris(Set 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 0000000000..55e351a140 --- /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 Bill Burke + * @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 0000000000..78c36d4020 --- /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 Bill Burke + * @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 requiredCredentials = new ArrayList(); + + + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true) + @JoinTable(name="AuthProviders") + List authenticationProviders = new ArrayList(); + + @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true) + Collection applications = new ArrayList(); + + @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") + Collection roles = new ArrayList(); + + @ElementCollection + @MapKeyColumn(name="name") + @Column(name="value") + @CollectionTable + protected Map smtpConfig = new HashMap(); + + @ElementCollection + @MapKeyColumn(name="name") + @Column(name="value") + @CollectionTable + protected Map socialConfig = new HashMap(); + + @ElementCollection + @MapKeyColumn(name="name") + @Column(name="value") + @CollectionTable + protected Map ldapServerConfig = new HashMap(); + + @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true) + @JoinTable(name="RealmDefaultRoles") + protected Collection defaultRoles = new ArrayList(); + + protected boolean auditEnabled; + protected long auditExpiration; + + @ElementCollection + protected Set auditListeners= new HashSet(); + + @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 getRequiredCredentials() { + return requiredCredentials; + } + + public void setRequiredCredentials(Collection requiredCredentials) { + this.requiredCredentials = requiredCredentials; + } + + public List getAuthenticationProviders() { + return authenticationProviders; + } + + public void setAuthenticationProviders(List authenticationProviders) { + this.authenticationProviders = authenticationProviders; + } + + public Collection getApplications() { + return applications; + } + + public void setApplications(Collection applications) { + this.applications = applications; + } + + public Collection getRoles() { + return roles; + } + + public void setRoles(Collection roles) { + this.roles = roles; + } + + public void addRole(RoleEntity role) { + if (roles == null) { + roles = new ArrayList(); + } + roles.add(role); + } + + public Map getSmtpConfig() { + return smtpConfig; + } + + public void setSmtpConfig(Map smtpConfig) { + this.smtpConfig = smtpConfig; + } + + public Map getSocialConfig() { + return socialConfig; + } + + public void setSocialConfig(Map socialConfig) { + this.socialConfig = socialConfig; + } + + public Map getLdapServerConfig() { + return ldapServerConfig; + } + + public void setLdapServerConfig(Map ldapServerConfig) { + this.ldapServerConfig = ldapServerConfig; + } + + public Collection getDefaultRoles() { + return defaultRoles; + } + + public void setDefaultRoles(Collection 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 getAuditListeners() { + return auditListeners; + } + + public void setAuditListeners(Set 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 0000000000..6c14c86ff9 --- /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 Bill Burke + * @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 0000000000..36775ad2fa --- /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 Bill Burke + * @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 compositeRoles = new ArrayList(); + + 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 getCompositeRoles() { + return compositeRoles; + } + + public void setCompositeRoles(Collection 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 0000000000..b0b65a23d0 --- /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 Bill Burke + * @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/utils/JpaIdGenerator.java b/model/realms-jpa/src/main/java/org/keycloak/models/realms/jpa/utils/JpaIdGenerator.java new file mode 100644 index 0000000000..5ff77d5306 --- /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 Marek Posolda + */ +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 0000000000..81ec90a1ac --- /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 0000000000..a150d05038 --- /dev/null +++ b/model/realms-jpa/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,70 @@ + + + org.hibernate.ejb.HibernatePersistence + + org.keycloak.models.jpa.entities.ApplicationEntity + org.keycloak.models.jpa.entities.CredentialEntity + org.keycloak.models.jpa.entities.OAuthClientEntity + org.keycloak.models.jpa.entities.RealmEntity + org.keycloak.models.jpa.entities.RequiredCredentialEntity + org.keycloak.models.jpa.entities.AuthenticationProviderEntity + org.keycloak.models.jpa.entities.RoleEntity + org.keycloak.models.jpa.entities.SocialLinkEntity + org.keycloak.models.jpa.entities.AuthenticationLinkEntity + org.keycloak.models.jpa.entities.UserEntity + org.keycloak.models.jpa.entities.UserSessionEntity + org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity + org.keycloak.models.jpa.entities.UserRoleMappingEntity + org.keycloak.models.jpa.entities.UsernameLoginFailureEntity + org.keycloak.models.jpa.entities.ScopeMappingEntity + + true + + + + + + + + + + + + + + diff --git a/model/sessions-jpa/pom.xml b/model/sessions-jpa/pom.xml new file mode 100755 index 0000000000..4eb7265471 --- /dev/null +++ b/model/sessions-jpa/pom.xml @@ -0,0 +1,45 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-sessions-jpa + Keycloak Model Sessions JPA + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + + + org.keycloak + keycloak-model-hybrid + ${project.version} + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + provided + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymanager.version} + provided + + + 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 0000000000..09f92c0fc9 --- /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 Bill Burke + * @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 0000000000..9511fcdea3 --- /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 Stian Thorgersen + */ +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 getAllUserLoginFailures(String realm) { + TypedQuery query = em.createNamedQuery("getAllFailures", UsernameLoginFailureEntity.class); + List entities = query.getResultList(); + List models = new ArrayList(); + 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 getUserSessionsByUser(String user, String realm) { + List sessions = new LinkedList(); + for (UserSessionEntity e : em.createNamedQuery("getUserSessionByUser", UserSessionEntity.class) + .setParameter("userId", user).getResultList()) { + sessions.add(new UserSessionAdapter(em, realm, e)); + } + return sessions; + } + + @Override + public Set getUserSessionsByClient(String realm, String client) { + Set list = new HashSet(); + TypedQuery query = em.createNamedQuery("getClientUserSessionByClient", ClientUserSessionAssociationEntity.class); + query.setParameter("clientId", client); + List 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 query = em.createNamedQuery("getUserSessionExpired", UserSessionEntity.class) + .setParameter("maxTime", sessionTimeout) + .setParameter("idleTime", refreshTimeout); + List 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 0000000000..c0c0cde1d5 --- /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 Stian Thorgersen + */ +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 0000000000..2d27df0c74 --- /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 Stian Thorgersen + */ +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/UserSessionAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UserSessionAdapter.java new file mode 100755 index 0000000000..37e93bdc1c --- /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 Stian Thorgersen + */ +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 getClientAssociations() { + List clients = new ArrayList(); + 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/UsernameLoginFailureAdapter.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/UsernameLoginFailureAdapter.java new file mode 100755 index 0000000000..8593096fbc --- /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 Bill Burke + * @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/entities/ClientUserSessionAssociationEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/ClientUserSessionAssociationEntity.java new file mode 100755 index 0000000000..7a018eb88f --- /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 Bill Burke + * @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/UserSessionEntity.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/entities/UserSessionEntity.java new file mode 100755 index 0000000000..444afbec40 --- /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 Stian Thorgersen + */ +@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 clients = new ArrayList(); + + + 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 getClients() { + return clients; + } + + public void setClients(Collection clients) { + this.clients = clients; + } + +} 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 0000000000..a550e8fb1a --- /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 Bill Burke + * @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/utils/JpaIdGenerator.java b/model/sessions-jpa/src/main/java/org/keycloak/models/sessions/jpa/utils/JpaIdGenerator.java new file mode 100644 index 0000000000..3fc63e7907 --- /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 Marek Posolda + */ +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 0000000000..c086c02eaa --- /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 diff --git a/model/sessions-mem/pom.xml b/model/sessions-mem/pom.xml new file mode 100755 index 0000000000..322fc5ff83 --- /dev/null +++ b/model/sessions-mem/pom.xml @@ -0,0 +1,35 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-sessions-mem + Keycloak Model Sessions Mem + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + + + org.keycloak + keycloak-model-hybrid + ${project.version} + + + + 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 0000000000..f9054b5c0c --- /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 Stian Thorgersen + */ +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 lastIpFailure = new AtomicReference(); + + 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 0000000000..b0ab77b3c2 --- /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 Stian Thorgersen + */ +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 0000000000..031447dcce --- /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 Stian Thorgersen + */ +public class MemSessionProvider implements SessionProvider { + + private final ConcurrentHashMap sessions; + private final ConcurrentHashMap loginFailures; + private DummyKeycloakTransaction tx; + + public MemSessionProvider(ConcurrentHashMap sessions, ConcurrentHashMap 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 getAllUserLoginFailures(String realm) { + List failures = new LinkedList(); + 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 getUserSessionsByUser(String user, String realm) { + List userSessions = new LinkedList(); + for (SessionAdapter s : sessions.values()) { + if (s.getRealm().equals(realm) && s.getUser().equals(user)) { + userSessions.add(s); + } + } + return userSessions; + } + + @Override + public Set getUserSessionsByClient(String realm, String client) { + Set clientSessions = new HashSet(); + 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 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 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 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 0000000000..7eccae0a22 --- /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 Stian Thorgersen + */ +public class MemSessionProviderFactory implements SessionProviderFactory { + + private ConcurrentHashMap sessions = new ConcurrentHashMap(); + private ConcurrentHashMap loginFailures = new ConcurrentHashMap(); + + @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 0000000000..0dbd47d1aa --- /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 Stian Thorgersen + */ +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 clients = new LinkedList(); + + 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 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 0000000000..fb0d4e705d --- /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 Stian Thorgersen + */ +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 0000000000..407b73d4c8 --- /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 diff --git a/model/tests-hybrid/pom.xml b/model/tests-hybrid/pom.xml new file mode 100755 index 0000000000..8db100cd30 --- /dev/null +++ b/model/tests-hybrid/pom.xml @@ -0,0 +1,55 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-tests-hybrid + Keycloak Model Tests + + + + + org.keycloak + keycloak-core + ${project.version} + compile + + + org.keycloak + keycloak-model-api + ${project.version} + compile + + + org.keycloak + keycloak-model-hybrid + ${project.version} + compile + + + junit + junit + compile + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + 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 0000000000..361b953682 --- /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 Stian Thorgersen + */ +public abstract class AbstractUserProviderTest { + + private ProviderFactory factory; + private UserProvider provider; + private String userId; + private String userId2; + private String userId3; + + @Before + public void before() { + String providerId = getProviderId(); + ServiceLoader 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 roles = new HashSet(); + 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 roles2 = new HashSet(); + 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 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"); + } + +} diff --git a/model/users-jpa/pom.xml b/model/users-jpa/pom.xml new file mode 100755 index 0000000000..46ca527067 --- /dev/null +++ b/model/users-jpa/pom.xml @@ -0,0 +1,79 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-model-users-jpa + Keycloak Model Users JPA + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + + + org.keycloak + keycloak-model-hybrid + ${project.version} + + + org.hibernate.javax.persistence + hibernate-jpa-2.0-api + provided + + + org.hibernate + hibernate-entitymanager + ${hibernate.entitymanager.version} + provided + + + org.keycloak + keycloak-model-tests-hybrid + ${project.version} + test + + + com.h2database + h2 + test + + + org.bouncycastle + bcprov-jdk16 + test + + + net.iharder + base64 + test + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + + + 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 0000000000..2d1ab13f87 --- /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 Bill Burke + * @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 0000000000..204ab33f72 --- /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 Bill Burke + * @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 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 query = em.createNamedQuery("getRealmUserByLoginName", UserEntity.class); + query.setParameter("username", name); + query.setParameter("realm", realm); + List 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 query = em.createNamedQuery("getRealmUserByUsername", UserEntity.class); + query.setParameter("username", username); + query.setParameter("realm", realm); + List 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 query = em.createNamedQuery("getRealmUserByEmail", UserEntity.class); + query.setParameter("email", email); + query.setParameter("realm", realm); + List 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 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 getUsers(String realm) { + TypedQuery query = em.createQuery("select u from UserEntity u where u.realm = :realm", UserEntity.class); + query.setParameter("realm", realm); + List results = query.getResultList(); + List users = new ArrayList(); + for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity)); + return users; + } + + @Override + public List searchForUser(String search, String realm) { + TypedQuery 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 results = query.getResultList(); + List users = new ArrayList(); + for (UserEntity entity : results) users.add(new UserAdapter(realm, em, entity)); + return users; + } + + @Override + public List searchForUserByAttributes(Map attributes, String realm) { + StringBuilder builder = new StringBuilder("select u from UserEntity u"); + boolean first = true; + for (Map.Entry 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 query = em.createQuery(q, UserEntity.class); + query.setParameter("realm", realm); + List results = query.getResultList(); + List users = new ArrayList(); + 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 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 0000000000..64e8b06248 --- /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 Bill Burke + * @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 0000000000..ddd1c58052 --- /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 Stian Thorgersen + */ +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 0000000000..2851379ae8 --- /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 Bill Burke + * @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 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 getAttributes() { + Map result = new HashMap(); + for (UserAttributeEntity a : user.getAttributes()) { + result.put(a.getName(), a.getValue()); + } + return result; + } + + @Override + public void removeAttribute(String name) { + Iterator 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 getCredentials() { + List result = new ArrayList(); + 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 getRoleMappings() { + Set roles = new HashSet(); + for (UserRoleMappingEntity r : user.getRoles()) { + roles.add(r.getRole()); + } + return roles; + } + + @Override + public void deleteRoleMapping(String role) { + Iterator 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/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 0000000000..c888b55613 --- /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 Stian Thorgersen + */ +@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 0000000000..3d0175f793 --- /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 Bill Burke + * @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 0000000000..09f8788ae6 --- /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 Bill Burke + * @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 attributes = new LinkedList(); + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy="user") + protected List credentials = new LinkedList(); + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy="user") + protected List roles = new LinkedList(); + + 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 getAttributes() { + return attributes; + } + + public void setAttributes(List attributes) { + this.attributes = attributes; + } + + public String getRealm() { + return realm; + } + + public void setRealm(String realm) { + this.realm = realm; + } + + public Collection getCredentials() { + return credentials; + } + + public void setCredentials(List credentials) { + this.credentials = credentials; + } + + public List getRoles() { + return roles; + } + + public void setRoles(List 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 0000000000..513163e624 --- /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 Stian Thorgersen + */ +@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/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 0000000000..43ec95f86a --- /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 0000000000..0d1473967e --- /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 Stian Thorgersen + */ +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 0000000000..208c895dce --- /dev/null +++ b/model/users-jpa/src/test/resources/META-INF/persistence.xml @@ -0,0 +1,25 @@ + + + org.hibernate.ejb.HibernatePersistence + + org.keycloak.models.users.jpa.entities.UserAttributeEntity + org.keycloak.models.users.jpa.entities.UserCredentialEntity + org.keycloak.models.users.jpa.entities.UserRoleMappingEntity + org.keycloak.models.users.jpa.entities.UserEntity + + true + + + + + + + + + + + + diff --git a/server/pom.xml b/server/pom.xml index 2d8e61d684..d0318f4d45 100755 --- a/server/pom.xml +++ b/server/pom.xml @@ -49,7 +49,27 @@ org.keycloak - keycloak-model-jpa + keycloak-model-hybrid + ${project.version} + + + org.keycloak + keycloak-model-realms-jpa + ${project.version} + + + org.keycloak + keycloak-model-users-jpa + ${project.version} + + + org.keycloak + keycloak-model-sessions-mem + ${project.version} + + + org.keycloak + keycloak-model-sessions-jpa ${project.version} diff --git a/server/src/main/resources/META-INF/keycloak-server.json b/server/src/main/resources/META-INF/keycloak-server.json index 7e3f2465b8..6133aa4876 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 a7040140f5..bba0831dc6 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"> java:jboss/datasources/ExampleDS - org.keycloak.models.jpa.entities.ApplicationEntity - org.keycloak.models.jpa.entities.CredentialEntity - org.keycloak.models.jpa.entities.OAuthClientEntity - org.keycloak.models.jpa.entities.RealmEntity - org.keycloak.models.jpa.entities.RequiredCredentialEntity - org.keycloak.models.jpa.entities.AuthenticationProviderEntity - org.keycloak.models.jpa.entities.RoleEntity - org.keycloak.models.jpa.entities.SocialLinkEntity - org.keycloak.models.jpa.entities.AuthenticationLinkEntity - org.keycloak.models.jpa.entities.UserEntity - org.keycloak.models.jpa.entities.UserSessionEntity - org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity - org.keycloak.models.jpa.entities.UsernameLoginFailureEntity - org.keycloak.models.jpa.entities.UserRoleMappingEntity - org.keycloak.models.jpa.entities.ScopeMappingEntity + + org.keycloak.models.realms.jpa.entities.ApplicationEntity + org.keycloak.models.realms.jpa.entities.OAuthClientEntity + org.keycloak.models.realms.jpa.entities.RealmEntity + org.keycloak.models.realms.jpa.entities.RequiredCredentialEntity + org.keycloak.models.realms.jpa.entities.AuthenticationProviderEntity + org.keycloak.models.realms.jpa.entities.RoleEntity + org.keycloak.models.realms.jpa.entities.ScopeMappingEntity + + org.keycloak.models.users.jpa.entities.UserAttributeEntity + org.keycloak.models.users.jpa.entities.UserCredentialEntity + org.keycloak.models.users.jpa.entities.UserRoleMappingEntity + org.keycloak.models.users.jpa.entities.UserEntity + + org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity + org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity + org.keycloak.models.sessions.jpa.entities.UserSessionEntity true @@ -26,7 +28,7 @@ - + java:jboss/datasources/ExampleDS org.keycloak.audit.jpa.EventEntity @@ -37,5 +39,4 @@ - 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 16417579fc..2bf2dbdbce 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 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(); } diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml index 052f6b1945..50466c81b6 100755 --- a/testsuite/integration/pom.xml +++ b/testsuite/integration/pom.xml @@ -122,7 +122,27 @@ org.keycloak - keycloak-model-jpa + keycloak-model-hybrid + ${project.version} + + + org.keycloak + keycloak-model-realms-jpa + ${project.version} + + + org.keycloak + keycloak-model-users-jpa + ${project.version} + + + org.keycloak + keycloak-model-sessions-mem + ${project.version} + + + org.keycloak + keycloak-model-sessions-jpa ${project.version} 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 9204d6f654..c39a794003 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 39dd390bc9..5a261d7237 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 @@ org.hibernate.ejb.HibernatePersistence - org.keycloak.models.jpa.entities.ApplicationEntity - org.keycloak.models.jpa.entities.CredentialEntity - org.keycloak.models.jpa.entities.OAuthClientEntity - org.keycloak.models.jpa.entities.RealmEntity - org.keycloak.models.jpa.entities.RequiredCredentialEntity - org.keycloak.models.jpa.entities.AuthenticationProviderEntity - org.keycloak.models.jpa.entities.RoleEntity - org.keycloak.models.jpa.entities.SocialLinkEntity - org.keycloak.models.jpa.entities.AuthenticationLinkEntity - org.keycloak.models.jpa.entities.UserEntity - org.keycloak.models.jpa.entities.ClientUserSessionAssociationEntity - org.keycloak.models.jpa.entities.UserSessionEntity - org.keycloak.models.jpa.entities.UsernameLoginFailureEntity - org.keycloak.models.jpa.entities.UserRoleMappingEntity - org.keycloak.models.jpa.entities.ScopeMappingEntity + org.keycloak.models.realms.jpa.entities.ApplicationEntity + org.keycloak.models.realms.jpa.entities.OAuthClientEntity + org.keycloak.models.realms.jpa.entities.RealmEntity + org.keycloak.models.realms.jpa.entities.RequiredCredentialEntity + org.keycloak.models.realms.jpa.entities.AuthenticationProviderEntity + org.keycloak.models.realms.jpa.entities.RoleEntity + org.keycloak.models.realms.jpa.entities.ScopeMappingEntity + + org.keycloak.models.users.jpa.entities.UserAttributeEntity + org.keycloak.models.users.jpa.entities.UserCredentialEntity + org.keycloak.models.users.jpa.entities.UserRoleMappingEntity + org.keycloak.models.users.jpa.entities.UserEntity + + org.keycloak.models.sessions.jpa.entities.ClientUserSessionAssociationEntity + org.keycloak.models.sessions.jpa.entities.UsernameLoginFailureEntity + org.keycloak.models.sessions.jpa.entities.UserSessionEntity true @@ -51,38 +52,4 @@ - - diff --git a/testsuite/performance/pom.xml b/testsuite/performance/pom.xml index 1ba9c0fc8c..201abf381a 100755 --- a/testsuite/performance/pom.xml +++ b/testsuite/performance/pom.xml @@ -40,6 +40,31 @@ keycloak-model-jpa ${project.version} + + org.keycloak + keycloak-model-hybrid + ${project.version} + + + org.keycloak + keycloak-model-realms-jpa + ${project.version} + + + org.keycloak + keycloak-model-users-jpa + ${project.version} + + + org.keycloak + keycloak-model-sessions-mem + ${project.version} + + + org.keycloak + keycloak-model-sessions-jpa + ${project.version} + org.keycloak keycloak-model-mongo diff --git a/testsuite/tools/pom.xml b/testsuite/tools/pom.xml index c630624317..31d14921ef 100755 --- a/testsuite/tools/pom.xml +++ b/testsuite/tools/pom.xml @@ -66,6 +66,26 @@ keycloak-model-jpa ${project.version} + + org.keycloak + keycloak-model-hybrid + ${project.version} + + + org.keycloak + keycloak-model-realms-jpa + ${project.version} + + + org.keycloak + keycloak-model-users-jpa + ${project.version} + + + org.keycloak + keycloak-model-sessions-mem + ${project.version} + org.keycloak keycloak-audit-api