Authentication SPI. Implementations based on Picketlink+LDAP, model and external model (other realm). Added KeycloakRegistry
This commit is contained in:
parent
0879013a90
commit
793f69d4b6
79 changed files with 3088 additions and 81 deletions
|
@ -0,0 +1,26 @@
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationLinkRepresentation {
|
||||||
|
|
||||||
|
private String authProvider;
|
||||||
|
private String authUserId;
|
||||||
|
|
||||||
|
public String getAuthProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthProvider(String authProvider) {
|
||||||
|
this.authProvider = authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthUserId() {
|
||||||
|
return authUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthUserId(String authUserId) {
|
||||||
|
this.authUserId = authUserId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,39 @@
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationMappingRepresentation {
|
||||||
|
|
||||||
|
protected String self; // link
|
||||||
|
protected String username;
|
||||||
|
protected List<AuthenticationLinkRepresentation> authenticationLinks;
|
||||||
|
|
||||||
|
public String getSelf() {
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSelf(String self) {
|
||||||
|
this.self = self;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AuthenticationLinkRepresentation> getAuthenticationLinks() {
|
||||||
|
return authenticationLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationLinks(List<AuthenticationLinkRepresentation> authenticationLinks) {
|
||||||
|
this.authenticationLinks = authenticationLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.keycloak.representations.idm;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationProviderRepresentation {
|
||||||
|
|
||||||
|
private String providerName;
|
||||||
|
private boolean passwordUpdateSupported = true;
|
||||||
|
private Map<String, String> config;
|
||||||
|
|
||||||
|
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 Map<String, String> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(Map<String, String> config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -39,10 +39,13 @@ public class RealmRepresentation {
|
||||||
protected Map<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
|
protected Map<String, List<UserRoleMappingRepresentation>> applicationRoleMappings;
|
||||||
protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
|
protected Map<String, List<ScopeMappingRepresentation>> applicationScopeMappings;
|
||||||
protected List<SocialMappingRepresentation> socialMappings;
|
protected List<SocialMappingRepresentation> socialMappings;
|
||||||
|
protected List<AuthenticationMappingRepresentation> authenticationMappings;
|
||||||
protected List<ApplicationRepresentation> applications;
|
protected List<ApplicationRepresentation> applications;
|
||||||
protected List<OAuthClientRepresentation> oauthClients;
|
protected List<OAuthClientRepresentation> oauthClients;
|
||||||
protected Map<String, String> socialProviders;
|
protected Map<String, String> socialProviders;
|
||||||
protected Map<String, String> smtpServer;
|
protected Map<String, String> smtpServer;
|
||||||
|
protected Map<String, String> ldapServer;
|
||||||
|
protected List<AuthenticationProviderRepresentation> authenticationProviders;
|
||||||
protected String loginTheme;
|
protected String loginTheme;
|
||||||
protected String accountTheme;
|
protected String accountTheme;
|
||||||
|
|
||||||
|
@ -178,6 +181,18 @@ public class RealmRepresentation {
|
||||||
return mapping;
|
return mapping;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<AuthenticationMappingRepresentation> getAuthenticationMappings() {
|
||||||
|
return authenticationMappings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationMappingRepresentation authenticationMapping(String username) {
|
||||||
|
AuthenticationMappingRepresentation mapping = new AuthenticationMappingRepresentation();
|
||||||
|
mapping.setUsername(username);
|
||||||
|
if (authenticationMappings == null) authenticationMappings = new ArrayList<AuthenticationMappingRepresentation>();
|
||||||
|
authenticationMappings.add(mapping);
|
||||||
|
return mapping;
|
||||||
|
}
|
||||||
|
|
||||||
public Set<String> getRequiredCredentials() {
|
public Set<String> getRequiredCredentials() {
|
||||||
return requiredCredentials;
|
return requiredCredentials;
|
||||||
}
|
}
|
||||||
|
@ -298,6 +313,22 @@ public class RealmRepresentation {
|
||||||
this.smtpServer = smtpServer;
|
this.smtpServer = smtpServer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getLdapServer() {
|
||||||
|
return ldapServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLdapServer(Map<String, String> ldapServer) {
|
||||||
|
this.ldapServer = ldapServer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<AuthenticationProviderRepresentation> getAuthenticationProviders() {
|
||||||
|
return authenticationProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationProviders(List<AuthenticationProviderRepresentation> authenticationProviders) {
|
||||||
|
this.authenticationProviders = authenticationProviders;
|
||||||
|
}
|
||||||
|
|
||||||
public List<OAuthClientRepresentation> getOauthClients() {
|
public List<OAuthClientRepresentation> getOauthClients() {
|
||||||
return oauthClients;
|
return oauthClients;
|
||||||
}
|
}
|
||||||
|
|
29
core/src/main/java/org/keycloak/util/KeycloakRegistry.java
Normal file
29
core/src/main/java/org/keycloak/util/KeycloakRegistry.java
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package org.keycloak.util;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of services shared for whole Keycloak server
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class KeycloakRegistry {
|
||||||
|
|
||||||
|
private final ConcurrentMap<Class<?>, Object> services = new ConcurrentHashMap<Class<?>, Object>();
|
||||||
|
|
||||||
|
public <T> void putService(Class<T> type, T object) {
|
||||||
|
services.put(type, object);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T putServiceIfAbsent(Class<T> type, T object) {
|
||||||
|
// Put only if absent and always return the version from registry
|
||||||
|
services.putIfAbsent(type, object);
|
||||||
|
return (T) services.get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> T getService(Class<T> type) {
|
||||||
|
return (T) services.get(type);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Link between user and authentication provider
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationLinkModel {
|
||||||
|
|
||||||
|
private final String authProvider;
|
||||||
|
private final String authUserId;
|
||||||
|
|
||||||
|
public AuthenticationLinkModel(String authProvider, String authUserId) {
|
||||||
|
this.authProvider = authProvider;
|
||||||
|
this.authUserId = authUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthUserId() {
|
||||||
|
return authUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.keycloak.models;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationProviderModel {
|
||||||
|
|
||||||
|
private String providerName;
|
||||||
|
private boolean passwordUpdateSupported = true;
|
||||||
|
private Map<String, String> config;
|
||||||
|
|
||||||
|
public AuthenticationProviderModel(String providerName, boolean passwordUpdateSupported, Map<String, String> config) {
|
||||||
|
this.providerName = providerName;
|
||||||
|
this.passwordUpdateSupported = passwordUpdateSupported;
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 Map<String, String> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(Map<String, String> config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -134,6 +134,12 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
||||||
|
|
||||||
boolean removeSocialLink(UserModel user, String socialProvider);
|
boolean removeSocialLink(UserModel user, String socialProvider);
|
||||||
|
|
||||||
|
UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink);
|
||||||
|
|
||||||
|
Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user);
|
||||||
|
|
||||||
|
void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink);
|
||||||
|
|
||||||
boolean isSocial();
|
boolean isSocial();
|
||||||
|
|
||||||
void setSocial(boolean social);
|
void setSocial(boolean social);
|
||||||
|
@ -164,6 +170,14 @@ public interface RealmModel extends RoleContainerModel, RoleMapperModel, ScopeMa
|
||||||
|
|
||||||
void setSocialConfig(Map<String, String> socialConfig);
|
void setSocialConfig(Map<String, String> socialConfig);
|
||||||
|
|
||||||
|
Map<String, String> getLdapServerConfig();
|
||||||
|
|
||||||
|
void setLdapServerConfig(Map<String, String> ldapServerConfig);
|
||||||
|
|
||||||
|
List<AuthenticationProviderModel> getAuthenticationProviders();
|
||||||
|
|
||||||
|
void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders);
|
||||||
|
|
||||||
Set<RoleModel> getRealmRoleMappings(UserModel user);
|
Set<RoleModel> getRealmRoleMappings(UserModel user);
|
||||||
|
|
||||||
Set<RoleModel> getRealmScopeMappings(ClientModel client);
|
Set<RoleModel> getRealmScopeMappings(ClientModel client);
|
||||||
|
|
|
@ -10,7 +10,9 @@ import java.util.UUID;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import org.bouncycastle.openssl.PEMWriter;
|
import org.bouncycastle.openssl.PEMWriter;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.util.PemUtils;
|
import org.keycloak.util.PemUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -23,8 +25,6 @@ public final class KeycloakModelUtils {
|
||||||
private KeycloakModelUtils() {
|
private KeycloakModelUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AtomicLong counter = new AtomicLong(1);
|
|
||||||
|
|
||||||
public static String generateId() {
|
public static String generateId() {
|
||||||
return UUID.randomUUID().toString();
|
return UUID.randomUUID().toString();
|
||||||
}
|
}
|
||||||
|
@ -84,4 +84,19 @@ public final class KeycloakModelUtils {
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to find user by given username. If it fails, then fallback to find him by email
|
||||||
|
*
|
||||||
|
* @param realm realm
|
||||||
|
* @param username username or email of user
|
||||||
|
* @return found user
|
||||||
|
*/
|
||||||
|
public static UserModel findUserByNameOrEmail(RealmModel realm, String username) {
|
||||||
|
UserModel user = realm.getUser(username);
|
||||||
|
if (user == null && username.contains("@")) {
|
||||||
|
user = realm.getUserByEmail(username);
|
||||||
|
}
|
||||||
|
return user;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,12 @@
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-tests</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-tests</artifactId>
|
<artifactId>keycloak-model-tests</artifactId>
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
package org.keycloak.models.jpa;
|
package org.keycloak.models.jpa;
|
||||||
|
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.RoleContainerModel;
|
import org.keycloak.models.RoleContainerModel;
|
||||||
import org.keycloak.models.jpa.entities.ApplicationEntity;
|
import org.keycloak.models.jpa.entities.ApplicationEntity;
|
||||||
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
|
import org.keycloak.models.jpa.entities.ApplicationRoleEntity;
|
||||||
|
import org.keycloak.models.jpa.entities.AuthenticationLinkEntity;
|
||||||
|
import org.keycloak.models.jpa.entities.AuthenticationProviderEntity;
|
||||||
import org.keycloak.models.jpa.entities.CredentialEntity;
|
import org.keycloak.models.jpa.entities.CredentialEntity;
|
||||||
import org.keycloak.models.jpa.entities.OAuthClientEntity;
|
import org.keycloak.models.jpa.entities.OAuthClientEntity;
|
||||||
import org.keycloak.models.jpa.entities.RealmEntity;
|
import org.keycloak.models.jpa.entities.RealmEntity;
|
||||||
|
@ -389,6 +393,7 @@ public class RealmAdapter implements RealmModel {
|
||||||
private void removeUser(UserEntity user) {
|
private void removeUser(UserEntity user) {
|
||||||
em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
em.createQuery("delete from " + UserRoleMappingEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||||
em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
em.createQuery("delete from " + SocialLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||||
|
em.createQuery("delete from " + AuthenticationLinkEntity.class.getSimpleName() + " where user = :user").setParameter("user", user).executeUpdate();
|
||||||
em.remove(user);
|
em.remove(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -606,6 +611,47 @@ public class RealmAdapter implements RealmModel {
|
||||||
return results.size() > 0 ? results.get(0) : null;
|
return results.size() > 0 ? results.get(0) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
|
||||||
|
TypedQuery<UserEntity> query = em.createNamedQuery("findUserByAuthLinkAndRealm", UserEntity.class);
|
||||||
|
query.setParameter("realm", realm);
|
||||||
|
query.setParameter("authProvider", authenticationLink.getAuthProvider());
|
||||||
|
query.setParameter("authUserId", authenticationLink.getAuthUserId());
|
||||||
|
List<UserEntity> results = query.getResultList();
|
||||||
|
if (results.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else if (results.size() > 1) {
|
||||||
|
throw new IllegalStateException("More results found for authenticationProvider=" + authenticationLink.getAuthProvider() +
|
||||||
|
", authUserId=" + authenticationLink.getAuthUserId() + ", results=" + results);
|
||||||
|
} else {
|
||||||
|
UserEntity user = results.get(0);
|
||||||
|
return new UserAdapter(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
|
||||||
|
TypedQuery<AuthenticationLinkEntity> query = em.createNamedQuery("findAuthLinkByUser", AuthenticationLinkEntity.class);
|
||||||
|
query.setParameter("user", ((UserAdapter) user).getUser());
|
||||||
|
List<AuthenticationLinkEntity> results = query.getResultList();
|
||||||
|
Set<AuthenticationLinkModel> set = new HashSet<AuthenticationLinkModel>();
|
||||||
|
for (AuthenticationLinkEntity entity : results) {
|
||||||
|
set.add(new AuthenticationLinkModel(entity.getAuthProvider(), entity.getAuthUserId()));
|
||||||
|
}
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
|
||||||
|
AuthenticationLinkEntity entity = new AuthenticationLinkEntity();
|
||||||
|
entity.setRealm(realm);
|
||||||
|
entity.setAuthProvider(authenticationLink.getAuthProvider());
|
||||||
|
entity.setAuthUserId(authenticationLink.getAuthUserId());
|
||||||
|
entity.setUser(((UserAdapter) user).getUser());
|
||||||
|
em.persist(entity);
|
||||||
|
em.flush();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isSocial() {
|
public boolean isSocial() {
|
||||||
return realm.isSocial();
|
return realm.isSocial();
|
||||||
|
@ -755,6 +801,56 @@ public class RealmAdapter implements RealmModel {
|
||||||
em.flush();
|
em.flush();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getLdapServerConfig() {
|
||||||
|
return realm.getLdapServerConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
|
||||||
|
realm.setLdapServerConfig(ldapServerConfig);
|
||||||
|
em.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuthenticationProviderModel> getAuthenticationProviders() {
|
||||||
|
Collection<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
|
||||||
|
List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
|
||||||
|
for (AuthenticationProviderEntity entity : entities) {
|
||||||
|
result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
|
||||||
|
List<AuthenticationProviderEntity> newEntities = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
for (AuthenticationProviderModel model : authenticationProviders) {
|
||||||
|
AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
|
||||||
|
entity.setProviderName(model.getProviderName());
|
||||||
|
entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
|
||||||
|
entity.setConfig(model.getConfig());
|
||||||
|
newEntities.add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove all existing first
|
||||||
|
Collection<AuthenticationProviderEntity> existing = realm.getAuthenticationProviders();
|
||||||
|
Collection<AuthenticationProviderEntity> copy = new ArrayList<AuthenticationProviderEntity>(existing);
|
||||||
|
for (AuthenticationProviderEntity apToRemove : copy) {
|
||||||
|
existing.remove(apToRemove);
|
||||||
|
em.remove(apToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now create all new providers
|
||||||
|
for (AuthenticationProviderEntity apToAdd : newEntities) {
|
||||||
|
existing.add(apToAdd);
|
||||||
|
em.persist(apToAdd);
|
||||||
|
}
|
||||||
|
|
||||||
|
em.flush();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RoleModel getRole(String name) {
|
public RoleModel getRole(String name) {
|
||||||
TypedQuery<RealmRoleEntity> query = em.createNamedQuery("getRealmRoleByName", RealmRoleEntity.class);
|
TypedQuery<RealmRoleEntity> query = em.createNamedQuery("getRealmRoleByName", RealmRoleEntity.class);
|
||||||
|
|
|
@ -0,0 +1,75 @@
|
||||||
|
package org.keycloak.models.jpa.entities;
|
||||||
|
|
||||||
|
import javax.persistence.Entity;
|
||||||
|
import javax.persistence.GeneratedValue;
|
||||||
|
import javax.persistence.Id;
|
||||||
|
import javax.persistence.ManyToOne;
|
||||||
|
import javax.persistence.NamedQueries;
|
||||||
|
import javax.persistence.NamedQuery;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@NamedQueries({
|
||||||
|
@NamedQuery(name="findAuthLinkByUser", query="select link from AuthenticationLinkEntity link where link.user = :user"),
|
||||||
|
@NamedQuery(name="findUserByAuthLinkAndRealm", query="select link.user from AuthenticationLinkEntity link where link.realm = :realm and link.authProvider = :authProvider and link.authUserId = :authUserId")
|
||||||
|
})
|
||||||
|
@Entity
|
||||||
|
public class AuthenticationLinkEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
||||||
|
@GeneratedValue(generator = "keycloak_generator")
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
private UserEntity user;
|
||||||
|
|
||||||
|
@ManyToOne
|
||||||
|
protected RealmEntity realm;
|
||||||
|
|
||||||
|
protected String authProvider;
|
||||||
|
protected String authUserId;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UserEntity getUser() {
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUser(UserEntity user) {
|
||||||
|
this.user = user;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RealmEntity getRealm() {
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRealm(RealmEntity realm) {
|
||||||
|
this.realm = realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthProvider(String authProvider) {
|
||||||
|
this.authProvider = authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAuthUserId() {
|
||||||
|
return authUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthUserId(String authUserId) {
|
||||||
|
this.authUserId = authUserId;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package org.keycloak.models.jpa.entities;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
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.MapKeyColumn;
|
||||||
|
|
||||||
|
import org.hibernate.annotations.GenericGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@Entity
|
||||||
|
public class AuthenticationProviderEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GenericGenerator(name="keycloak_generator", strategy="org.keycloak.models.jpa.utils.JpaIdGenerator")
|
||||||
|
@GeneratedValue(generator = "keycloak_generator")
|
||||||
|
protected String id;
|
||||||
|
|
||||||
|
private String providerName;
|
||||||
|
private boolean passwordUpdateSupported;
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@MapKeyColumn(name="name")
|
||||||
|
@Column(name="value")
|
||||||
|
@CollectionTable
|
||||||
|
private Map<String, String> config;
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProviderName() {
|
||||||
|
return providerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderName(String providerName) {
|
||||||
|
this.providerName = providerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isPasswordUpdateSupported() {
|
||||||
|
return passwordUpdateSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
|
||||||
|
this.passwordUpdateSupported = passwordUpdateSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(Map<String, String> config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,6 +63,10 @@ public class RealmEntity {
|
||||||
@JoinTable(name="User_RequiredCreds")
|
@JoinTable(name="User_RequiredCreds")
|
||||||
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
|
Collection<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
|
||||||
|
|
||||||
|
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
|
@JoinTable(name="AuthProviders")
|
||||||
|
Collection<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
|
||||||
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
|
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
|
||||||
|
|
||||||
|
@ -81,6 +85,12 @@ public class RealmEntity {
|
||||||
@CollectionTable
|
@CollectionTable
|
||||||
protected Map<String, String> socialConfig = new HashMap<String, String>();
|
protected Map<String, String> socialConfig = new HashMap<String, String>();
|
||||||
|
|
||||||
|
@ElementCollection
|
||||||
|
@MapKeyColumn(name="name")
|
||||||
|
@Column(name="value")
|
||||||
|
@CollectionTable
|
||||||
|
protected Map<String, String> ldapServerConfig = new HashMap<String, String>();
|
||||||
|
|
||||||
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
|
||||||
@JoinTable(name="RealmDefaultRoles")
|
@JoinTable(name="RealmDefaultRoles")
|
||||||
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
|
Collection<RoleEntity> defaultRoles = new ArrayList<RoleEntity>();
|
||||||
|
@ -229,6 +239,14 @@ public class RealmEntity {
|
||||||
this.requiredCredentials = requiredCredentials;
|
this.requiredCredentials = requiredCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<AuthenticationProviderEntity> getAuthenticationProviders() {
|
||||||
|
return authenticationProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationProviders(Collection<AuthenticationProviderEntity> authenticationProviders) {
|
||||||
|
this.authenticationProviders = authenticationProviders;
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<ApplicationEntity> getApplications() {
|
public Collection<ApplicationEntity> getApplications() {
|
||||||
return applications;
|
return applications;
|
||||||
}
|
}
|
||||||
|
@ -268,6 +286,14 @@ public class RealmEntity {
|
||||||
this.socialConfig = socialConfig;
|
this.socialConfig = socialConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getLdapServerConfig() {
|
||||||
|
return ldapServerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
|
||||||
|
this.ldapServerConfig = ldapServerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
public Collection<RoleEntity> getDefaultRoles() {
|
public Collection<RoleEntity> getDefaultRoles() {
|
||||||
return defaultRoles;
|
return defaultRoles;
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,9 +10,11 @@
|
||||||
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||||
|
|
|
@ -47,6 +47,12 @@
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-tests</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
<artifactId>keycloak-model-tests</artifactId>
|
<artifactId>keycloak-model-tests</artifactId>
|
||||||
|
|
|
@ -8,6 +8,8 @@ import org.keycloak.models.mongo.api.MongoStore;
|
||||||
import org.keycloak.models.mongo.impl.MongoStoreImpl;
|
import org.keycloak.models.mongo.impl.MongoStoreImpl;
|
||||||
import org.keycloak.models.mongo.keycloak.config.MongoClientProvider;
|
import org.keycloak.models.mongo.keycloak.config.MongoClientProvider;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
|
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
|
||||||
|
import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity;
|
||||||
|
import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
|
import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
|
import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
|
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
|
||||||
|
@ -29,8 +31,10 @@ public class MongoKeycloakSessionFactory implements KeycloakSessionFactory {
|
||||||
UserEntity.class,
|
UserEntity.class,
|
||||||
RoleEntity.class,
|
RoleEntity.class,
|
||||||
RequiredCredentialEntity.class,
|
RequiredCredentialEntity.class,
|
||||||
|
AuthenticationProviderEntity.class,
|
||||||
CredentialEntity.class,
|
CredentialEntity.class,
|
||||||
SocialLinkEntity.class,
|
SocialLinkEntity.class,
|
||||||
|
AuthenticationLinkEntity.class,
|
||||||
ApplicationEntity.class,
|
ApplicationEntity.class,
|
||||||
OAuthClientEntity.class
|
OAuthClientEntity.class
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,6 +4,8 @@ import com.mongodb.DBObject;
|
||||||
import com.mongodb.QueryBuilder;
|
import com.mongodb.QueryBuilder;
|
||||||
import org.jboss.logging.Logger;
|
import org.jboss.logging.Logger;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
import org.keycloak.models.OAuthClientModel;
|
||||||
import org.keycloak.models.PasswordPolicy;
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
@ -15,6 +17,8 @@ import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
import org.keycloak.models.mongo.api.context.MongoStoreInvocationContext;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
|
import org.keycloak.models.mongo.keycloak.entities.ApplicationEntity;
|
||||||
|
import org.keycloak.models.mongo.keycloak.entities.AuthenticationLinkEntity;
|
||||||
|
import org.keycloak.models.mongo.keycloak.entities.AuthenticationProviderEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
|
import org.keycloak.models.mongo.keycloak.entities.CredentialEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
|
import org.keycloak.models.mongo.keycloak.entities.OAuthClientEntity;
|
||||||
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
|
import org.keycloak.models.mongo.keycloak.entities.RealmEntity;
|
||||||
|
@ -387,7 +391,8 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
||||||
|
|
||||||
UserEntity userEntity = new UserEntity();
|
UserEntity userEntity = new UserEntity();
|
||||||
userEntity.setLoginName(username);
|
userEntity.setLoginName(username);
|
||||||
userEntity.setEnabled(true);
|
// Compatibility with JPA model, which has user disabled by default
|
||||||
|
// userEntity.setEnabled(true);
|
||||||
userEntity.setRealmId(getId());
|
userEntity.setRealmId(getId());
|
||||||
|
|
||||||
getMongoStore().insertEntity(userEntity, invocationContext);
|
getMongoStore().insertEntity(userEntity, invocationContext);
|
||||||
|
@ -920,6 +925,44 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserModel getUserByAuthenticationLink(AuthenticationLinkModel authenticationLink) {
|
||||||
|
DBObject query = new QueryBuilder()
|
||||||
|
.and("authenticationLinks.authProvider").is(authenticationLink.getAuthProvider())
|
||||||
|
.and("authenticationLinks.authUserId").is(authenticationLink.getAuthUserId())
|
||||||
|
.and("realmId").is(getId())
|
||||||
|
.get();
|
||||||
|
UserEntity userEntity = getMongoStore().loadSingleEntity(UserEntity.class, query, invocationContext);
|
||||||
|
return userEntity==null ? null : new UserAdapter(userEntity, invocationContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<AuthenticationLinkModel> getAuthenticationLinks(UserModel user) {
|
||||||
|
UserEntity userEntity = ((UserAdapter)user).getUser();
|
||||||
|
List<AuthenticationLinkEntity> linkEntities = userEntity.getAuthenticationLinks();
|
||||||
|
|
||||||
|
if (linkEntities == null) {
|
||||||
|
return Collections.EMPTY_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<AuthenticationLinkModel> result = new HashSet<AuthenticationLinkModel>();
|
||||||
|
for (AuthenticationLinkEntity authLinkEntity : linkEntities) {
|
||||||
|
AuthenticationLinkModel model = new AuthenticationLinkModel(authLinkEntity.getAuthProvider(), authLinkEntity.getAuthUserId());
|
||||||
|
result.add(model);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addAuthenticationLink(UserModel user, AuthenticationLinkModel authenticationLink) {
|
||||||
|
UserEntity userEntity = ((UserAdapter)user).getUser();
|
||||||
|
AuthenticationLinkEntity authLinkEntity = new AuthenticationLinkEntity();
|
||||||
|
authLinkEntity.setAuthProvider(authenticationLink.getAuthProvider());
|
||||||
|
authLinkEntity.setAuthUserId(authenticationLink.getAuthUserId());
|
||||||
|
|
||||||
|
getMongoStore().pushItemToList(userEntity, "authenticationLinks", authLinkEntity, true, invocationContext);
|
||||||
|
}
|
||||||
|
|
||||||
protected void updateRealm() {
|
protected void updateRealm() {
|
||||||
super.updateMongoEntity();
|
super.updateMongoEntity();
|
||||||
}
|
}
|
||||||
|
@ -1033,6 +1076,43 @@ public class RealmAdapter extends AbstractMongoAdapter<RealmEntity> implements R
|
||||||
updateRealm();
|
updateRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Map<String, String> getLdapServerConfig() {
|
||||||
|
return realm.getLdapServerConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
|
||||||
|
realm.setLdapServerConfig(ldapServerConfig);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<AuthenticationProviderModel> getAuthenticationProviders() {
|
||||||
|
List<AuthenticationProviderEntity> entities = realm.getAuthenticationProviders();
|
||||||
|
List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
|
||||||
|
for (AuthenticationProviderEntity entity : entities) {
|
||||||
|
result.add(new AuthenticationProviderModel(entity.getProviderName(), entity.isPasswordUpdateSupported(), entity.getConfig()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders) {
|
||||||
|
List<AuthenticationProviderEntity> entities = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
for (AuthenticationProviderModel model : authenticationProviders) {
|
||||||
|
AuthenticationProviderEntity entity = new AuthenticationProviderEntity();
|
||||||
|
entity.setProviderName(model.getProviderName());
|
||||||
|
entity.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
|
||||||
|
entity.setConfig(model.getConfig());
|
||||||
|
entities.add(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
realm.setAuthenticationProviders(entities);
|
||||||
|
updateRealm();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RealmEntity getMongoEntity() {
|
public RealmEntity getMongoEntity() {
|
||||||
return realm;
|
return realm;
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.MongoEntity;
|
||||||
|
import org.keycloak.models.mongo.api.MongoField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationLinkEntity implements MongoEntity {
|
||||||
|
|
||||||
|
private String authUserId;
|
||||||
|
private String authProvider;
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public String getAuthUserId() {
|
||||||
|
return authUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthUserId(String authUserId) {
|
||||||
|
this.authUserId = authUserId;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public String getAuthProvider() {
|
||||||
|
return authProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthProvider(String authProvider) {
|
||||||
|
this.authProvider = authProvider;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.keycloak.models.mongo.keycloak.entities;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.models.mongo.api.MongoEntity;
|
||||||
|
import org.keycloak.models.mongo.api.MongoField;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationProviderEntity implements MongoEntity {
|
||||||
|
|
||||||
|
private String providerName;
|
||||||
|
private boolean passwordUpdateSupported;
|
||||||
|
private Map<String, String> config;
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public String getProviderName() {
|
||||||
|
return providerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProviderName(String providerName) {
|
||||||
|
this.providerName = providerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public boolean isPasswordUpdateSupported() {
|
||||||
|
return passwordUpdateSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
|
||||||
|
this.passwordUpdateSupported = passwordUpdateSupported;
|
||||||
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public Map<String, String> getConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setConfig(Map<String, String> config) {
|
||||||
|
this.config = config;
|
||||||
|
}
|
||||||
|
}
|
|
@ -47,9 +47,11 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
||||||
private List<String> defaultRoles = new ArrayList<String>();
|
private List<String> defaultRoles = new ArrayList<String>();
|
||||||
|
|
||||||
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
|
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
|
||||||
|
private List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
|
||||||
|
|
||||||
private Map<String, String> smtpConfig = new HashMap<String, String>();
|
private Map<String, String> smtpConfig = new HashMap<String, String>();
|
||||||
private Map<String, String> socialConfig = new HashMap<String, String>();
|
private Map<String, String> socialConfig = new HashMap<String, String>();
|
||||||
|
private Map<String, String> ldapServerConfig;
|
||||||
|
|
||||||
@MongoField
|
@MongoField
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -249,6 +251,15 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
||||||
this.requiredCredentials = requiredCredentials;
|
this.requiredCredentials = requiredCredentials;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public List<AuthenticationProviderEntity> getAuthenticationProviders() {
|
||||||
|
return authenticationProviders;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationProviders(List<AuthenticationProviderEntity> authenticationProviders) {
|
||||||
|
this.authenticationProviders = authenticationProviders;
|
||||||
|
}
|
||||||
|
|
||||||
@MongoField
|
@MongoField
|
||||||
public Map<String, String> getSmtpConfig() {
|
public Map<String, String> getSmtpConfig() {
|
||||||
return smtpConfig;
|
return smtpConfig;
|
||||||
|
@ -267,6 +278,15 @@ public class RealmEntity extends AbstractMongoIdentifiableEntity implements Mong
|
||||||
this.socialConfig = socialConfig;
|
this.socialConfig = socialConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public Map<String, String> getLdapServerConfig() {
|
||||||
|
return ldapServerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLdapServerConfig(Map<String, String> ldapServerConfig) {
|
||||||
|
this.ldapServerConfig = ldapServerConfig;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRemove(MongoStoreInvocationContext context) {
|
public void afterRemove(MongoStoreInvocationContext context) {
|
||||||
DBObject query = new QueryBuilder()
|
DBObject query = new QueryBuilder()
|
||||||
|
|
|
@ -58,10 +58,10 @@ public class SocialLinkEntity implements MongoEntity {
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int code = 1;
|
int code = 1;
|
||||||
if (socialUserId != null) {
|
if (socialUserId != null) {
|
||||||
code = code * 13;
|
code = code * socialUserId.hashCode() * 13;
|
||||||
}
|
}
|
||||||
if (socialProvider != null) {
|
if (socialProvider != null) {
|
||||||
code = code * 17;
|
code = code * socialProvider.hashCode() * 17;
|
||||||
}
|
}
|
||||||
return code;
|
return code;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,6 +33,7 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
||||||
private List<UserModel.RequiredAction> requiredActions;
|
private List<UserModel.RequiredAction> requiredActions;
|
||||||
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
|
||||||
private List<SocialLinkEntity> socialLinks;
|
private List<SocialLinkEntity> socialLinks;
|
||||||
|
private List<AuthenticationLinkEntity> authenticationLinks;
|
||||||
|
|
||||||
@MongoField
|
@MongoField
|
||||||
public String getLoginName() {
|
public String getLoginName() {
|
||||||
|
@ -160,4 +161,13 @@ public class UserEntity extends AbstractMongoIdentifiableEntity implements Mongo
|
||||||
public void setSocialLinks(List<SocialLinkEntity> socialLinks) {
|
public void setSocialLinks(List<SocialLinkEntity> socialLinks) {
|
||||||
this.socialLinks = socialLinks;
|
this.socialLinks = socialLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@MongoField
|
||||||
|
public List<AuthenticationLinkEntity> getAuthenticationLinks() {
|
||||||
|
return authenticationLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthenticationLinks(List<AuthenticationLinkEntity> authenticationLinks) {
|
||||||
|
this.authenticationLinks = authenticationLinks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,24 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-spi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-model</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-picketlink</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>junit</groupId>
|
<groupId>junit</groupId>
|
||||||
<artifactId>junit</artifactId>
|
<artifactId>junit</artifactId>
|
||||||
|
@ -36,6 +54,50 @@
|
||||||
<artifactId>jackson-mapper-asl</artifactId>
|
<artifactId>jackson-mapper-asl</artifactId>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- picketlink dependencies -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-common</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-api</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-impl</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-simple-schema</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketbox</groupId>
|
||||||
|
<artifactId>picketbox-ldap</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketbox</groupId>
|
||||||
|
<artifactId>picketbox-ldap</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
<type>test-jar</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
package org.keycloak.model.test;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Hashtable;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import javax.naming.CompositeName;
|
||||||
|
import javax.naming.Context;
|
||||||
|
import javax.naming.ContextNotEmptyException;
|
||||||
|
import javax.naming.Name;
|
||||||
|
import javax.naming.NameClassPair;
|
||||||
|
import javax.naming.NamingEnumeration;
|
||||||
|
import javax.naming.NamingException;
|
||||||
|
import javax.naming.directory.DirContext;
|
||||||
|
import javax.naming.directory.InitialDirContext;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.spi.picketlink.impl.LdapConstants;
|
||||||
|
import org.picketbox.test.ldap.AbstractLDAPTest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Forked from Picketlink project
|
||||||
|
*
|
||||||
|
* Abstract base for all LDAP test suites. It handles
|
||||||
|
* @author Peter Skopek: pskopek at redhat dot com
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class LDAPEmbeddedServer extends AbstractLDAPTest {
|
||||||
|
|
||||||
|
public static final String BASE_DN = "dc=keycloak,dc=org";
|
||||||
|
public static final String LDAP_URL = "ldap://localhost:10389";
|
||||||
|
public static final String ROLES_DN_SUFFIX = "ou=Roles,dc=keycloak,dc=org";
|
||||||
|
public static final String GROUP_DN_SUFFIX = "ou=Groups,dc=keycloak,dc=org";
|
||||||
|
public static final String USER_DN_SUFFIX = "ou=People,dc=keycloak,dc=org";
|
||||||
|
public static final String AGENT_DN_SUFFIX = "ou=Agent,dc=keycloak,dc=org";
|
||||||
|
public static final String CUSTOM_ACCOUNT_DN_SUFFIX = "ou=CustomAccount,dc=keycloak,dc=org";
|
||||||
|
|
||||||
|
public static final String CONNECTION_PROPERTIES = "ldap/ldap-connection.properties";
|
||||||
|
|
||||||
|
protected String connectionUrl = LDAP_URL;
|
||||||
|
protected String baseDn = BASE_DN;
|
||||||
|
protected String userDnSuffix = USER_DN_SUFFIX;
|
||||||
|
protected String rolesDnSuffix = ROLES_DN_SUFFIX;
|
||||||
|
protected String groupDnSuffix = GROUP_DN_SUFFIX;
|
||||||
|
protected String agentDnSuffix = AGENT_DN_SUFFIX;
|
||||||
|
protected boolean startEmbeddedLdapLerver = true;
|
||||||
|
protected String bindDn = "uid=admin,ou=system";
|
||||||
|
protected String bindCredential = "secret";
|
||||||
|
|
||||||
|
public static String IDM_TEST_LDAP_CONNECTION_URL = "idm.test.ldap.connection.url";
|
||||||
|
public static String IDM_TEST_LDAP_BASE_DN = "idm.test.ldap.base.dn";
|
||||||
|
public static String IDM_TEST_LDAP_ROLES_DN_SUFFIX = "idm.test.ldap.roles.dn.suffix";
|
||||||
|
public static String IDM_TEST_LDAP_GROUP_DN_SUFFIX = "idm.test.ldap.group.dn.suffix";
|
||||||
|
public static String IDM_TEST_LDAP_USER_DN_SUFFIX = "idm.test.ldap.user.dn.suffix";
|
||||||
|
public static String IDM_TEST_LDAP_AGENT_DN_SUFFIX = "idm.test.ldap.agent.dn.suffix";
|
||||||
|
public static String IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER = "idm.test.ldap.start.embedded.ldap.server";
|
||||||
|
public static String IDM_TEST_LDAP_BIND_DN = "idm.test.ldap.bind.dn";
|
||||||
|
public static String IDM_TEST_LDAP_BIND_CREDENTIAL = "idm.test.ldap.bind.credential";
|
||||||
|
|
||||||
|
|
||||||
|
public LDAPEmbeddedServer() {
|
||||||
|
super();
|
||||||
|
loadConnectionProperties();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadConnectionProperties() {
|
||||||
|
Properties p = new Properties();
|
||||||
|
try {
|
||||||
|
InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream(CONNECTION_PROPERTIES);
|
||||||
|
p.load(is);
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectionUrl = p.getProperty(IDM_TEST_LDAP_CONNECTION_URL, LDAP_URL);
|
||||||
|
baseDn = p.getProperty(IDM_TEST_LDAP_BASE_DN, BASE_DN);
|
||||||
|
userDnSuffix = p.getProperty(IDM_TEST_LDAP_USER_DN_SUFFIX, USER_DN_SUFFIX);
|
||||||
|
rolesDnSuffix = p.getProperty(IDM_TEST_LDAP_ROLES_DN_SUFFIX, ROLES_DN_SUFFIX);
|
||||||
|
groupDnSuffix = p.getProperty(IDM_TEST_LDAP_GROUP_DN_SUFFIX, GROUP_DN_SUFFIX);
|
||||||
|
agentDnSuffix = p.getProperty(IDM_TEST_LDAP_AGENT_DN_SUFFIX, AGENT_DN_SUFFIX);
|
||||||
|
startEmbeddedLdapLerver = Boolean.parseBoolean(p.getProperty(IDM_TEST_LDAP_START_EMBEDDED_LDAP_SERVER, "true"));
|
||||||
|
bindDn = p.getProperty(IDM_TEST_LDAP_BIND_DN, bindDn);
|
||||||
|
bindCredential = p.getProperty(IDM_TEST_LDAP_BIND_CREDENTIAL, bindCredential);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setup() throws Exception {
|
||||||
|
// suppress emb. LDAP server start
|
||||||
|
if (isStartEmbeddedLdapLerver()) {
|
||||||
|
super.setup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void tearDown() throws Exception {
|
||||||
|
|
||||||
|
// clear data left in LDAP
|
||||||
|
DirContext ctx = getDirContext();
|
||||||
|
clearSubContexts(ctx, new CompositeName(baseDn));
|
||||||
|
|
||||||
|
// suppress emb. LDAP server stop
|
||||||
|
if (isStartEmbeddedLdapLerver()) {
|
||||||
|
super.tearDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private DirContext getDirContext() throws NamingException {
|
||||||
|
Hashtable<String, String> env = new Hashtable<String, String>();
|
||||||
|
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
|
||||||
|
env.put(Context.PROVIDER_URL, connectionUrl);
|
||||||
|
env.put(Context.SECURITY_PRINCIPAL, bindDn);
|
||||||
|
env.put(Context.SECURITY_CREDENTIALS, bindCredential);
|
||||||
|
DirContext ctx = new InitialDirContext(env);
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setupLdapInRealm(RealmModel realm) {
|
||||||
|
Map<String,String> ldapConfig = new HashMap<String,String>();
|
||||||
|
ldapConfig.put(LdapConstants.CONNECTION_URL, getConnectionUrl());
|
||||||
|
ldapConfig.put(LdapConstants.BASE_DN, getBaseDn());
|
||||||
|
ldapConfig.put(LdapConstants.BIND_DN, getBindDn());
|
||||||
|
ldapConfig.put(LdapConstants.BIND_CREDENTIAL, getBindCredential());
|
||||||
|
ldapConfig.put(LdapConstants.USER_DN_SUFFIX, getUserDnSuffix());
|
||||||
|
realm.setLdapServerConfig(ldapConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static void clearSubContexts(DirContext ctx, Name name) throws NamingException {
|
||||||
|
|
||||||
|
NamingEnumeration<NameClassPair> enumeration = null;
|
||||||
|
try {
|
||||||
|
enumeration = ctx.list(name);
|
||||||
|
while (enumeration.hasMore()) {
|
||||||
|
NameClassPair pair = enumeration.next();
|
||||||
|
Name childName = ctx.composeName(new CompositeName(pair.getName()), name);
|
||||||
|
try {
|
||||||
|
ctx.destroySubcontext(childName);
|
||||||
|
}
|
||||||
|
catch (ContextNotEmptyException e) {
|
||||||
|
clearSubContexts(ctx, childName);
|
||||||
|
ctx.destroySubcontext(childName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (NamingException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
try {
|
||||||
|
enumeration.close();
|
||||||
|
}
|
||||||
|
catch (Exception e) {
|
||||||
|
// Never mind this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConnectionUrl() {
|
||||||
|
return connectionUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBaseDn() {
|
||||||
|
return baseDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUserDnSuffix() {
|
||||||
|
return userDnSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRolesDnSuffix() {
|
||||||
|
return rolesDnSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getGroupDnSuffix() {
|
||||||
|
return groupDnSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAgentDnSuffix() {
|
||||||
|
return agentDnSuffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isStartEmbeddedLdapLerver() {
|
||||||
|
return startEmbeddedLdapLerver;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindDn() {
|
||||||
|
return bindDn;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getBindCredential() {
|
||||||
|
return bindCredential;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void importLDIF(String fileName) throws Exception {
|
||||||
|
// import LDIF only in case we are running against embedded LDAP server
|
||||||
|
if (isStartEmbeddedLdapLerver()) {
|
||||||
|
super.importLDIF(fileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void createBaseDN() throws Exception {
|
||||||
|
ds.createBaseDN("keycloak", "dc=keycloak,dc=org");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,8 +23,6 @@ import org.keycloak.util.JsonSerialization;
|
||||||
*/
|
*/
|
||||||
public class AbstractModelTest {
|
public class AbstractModelTest {
|
||||||
|
|
||||||
private final Logger log = Logger.getLogger(getClass());
|
|
||||||
|
|
||||||
protected KeycloakSessionFactory factory;
|
protected KeycloakSessionFactory factory;
|
||||||
protected KeycloakSession identitySession;
|
protected KeycloakSession identitySession;
|
||||||
protected RealmManager realmManager;
|
protected RealmManager realmManager;
|
||||||
|
|
|
@ -22,6 +22,7 @@ public class ApplicationModelTest extends AbstractModelTest {
|
||||||
private ApplicationManager appManager;
|
private ApplicationManager appManager;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@Override
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
super.before();
|
super.before();
|
||||||
appManager = new ApplicationManager(realmManager);
|
appManager = new ApplicationManager(realmManager);
|
||||||
|
|
|
@ -0,0 +1,76 @@
|
||||||
|
package org.keycloak.model.test;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthProvidersConfigTest extends AbstractModelTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConfiguration() {
|
||||||
|
// Create realm and add some providers and ldap config. Then commit
|
||||||
|
RealmModel realm = realmManager.createRealm("test");
|
||||||
|
|
||||||
|
Map<String, String> ldapConfig = new HashMap<String,String>();
|
||||||
|
ldapConfig.put("connectionUrl", "ldap://localhost:10389");
|
||||||
|
ldapConfig.put("baseDn", "dc=keycloak,dc=org");
|
||||||
|
realm.setLdapServerConfig(ldapConfig);
|
||||||
|
|
||||||
|
AuthenticationProviderModel ap1 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
|
||||||
|
AuthenticationProviderModel ap2 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, true, Collections.EMPTY_MAP);
|
||||||
|
AuthenticationProviderModel ap3 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
|
||||||
|
|
||||||
|
List<AuthenticationProviderModel> authProviders = new ArrayList<AuthenticationProviderModel>();
|
||||||
|
authProviders.add(ap1);
|
||||||
|
authProviders.add(ap2);
|
||||||
|
authProviders.add(ap3);
|
||||||
|
realm.setAuthenticationProviders(authProviders);
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Assert ldap config are same
|
||||||
|
RealmModel persisted = realmManager.getRealm(realm.getId());
|
||||||
|
Assert.assertEquals(persisted.getLdapServerConfig(), ldapConfig);
|
||||||
|
|
||||||
|
// Assert providers are same and in same order
|
||||||
|
List<AuthenticationProviderModel> persProviders = persisted.getAuthenticationProviders();
|
||||||
|
Assert.assertEquals(persProviders.size(), 3);
|
||||||
|
assertProviderEquals(persProviders.get(0), ap1);
|
||||||
|
assertProviderEquals(persProviders.get(1), ap2);
|
||||||
|
assertProviderEquals(persProviders.get(2), ap3);
|
||||||
|
|
||||||
|
// Update providers
|
||||||
|
authProviders = new ArrayList<AuthenticationProviderModel>();
|
||||||
|
authProviders.add(ap3);
|
||||||
|
authProviders.add(ap2);
|
||||||
|
authProviders.add(ap1);
|
||||||
|
persisted.setAuthenticationProviders(authProviders);
|
||||||
|
|
||||||
|
commit();
|
||||||
|
|
||||||
|
// Assert providers are same and in same order
|
||||||
|
persisted = realmManager.getRealm(realm.getId());
|
||||||
|
persProviders = persisted.getAuthenticationProviders();
|
||||||
|
Assert.assertEquals(persProviders.size(), 3);
|
||||||
|
assertProviderEquals(persProviders.get(0), ap3);
|
||||||
|
assertProviderEquals(persProviders.get(1), ap2);
|
||||||
|
assertProviderEquals(persProviders.get(2), ap1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertProviderEquals(AuthenticationProviderModel prov1, AuthenticationProviderModel prov2) {
|
||||||
|
Assert.assertEquals(prov1.getProviderName(), prov2.getProviderName());
|
||||||
|
Assert.assertEquals(prov1.isPasswordUpdateSupported(), prov2.isPasswordUpdateSupported());
|
||||||
|
Assert.assertEquals(prov1.getConfig(), prov2.getConfig());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package org.keycloak.model.test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedHashMap;
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthProvidersExternalModelTest extends AbstractModelTest {
|
||||||
|
|
||||||
|
private RealmModel realm1;
|
||||||
|
private RealmModel realm2;
|
||||||
|
private AuthenticationManager am;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Override
|
||||||
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
|
|
||||||
|
// Create 2 realms and user in realm1
|
||||||
|
realm1 = realmManager.createRealm("realm1");
|
||||||
|
realm2 = realmManager.createRealm("realm2");
|
||||||
|
realm1.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
|
realm2.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
|
|
||||||
|
UserModel john = realm1.addUser("john");
|
||||||
|
john.setEnabled(true);
|
||||||
|
john.setFirstName("John");
|
||||||
|
john.setLastName("Doe");
|
||||||
|
john.setEmail("john@email.org");
|
||||||
|
|
||||||
|
UserCredentialModel credential = new UserCredentialModel();
|
||||||
|
credential.setType(CredentialRepresentation.PASSWORD);
|
||||||
|
credential.setValue("password");
|
||||||
|
realm1.updateCredential(john, credential);
|
||||||
|
|
||||||
|
am = new AuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExternalModelPasswordValidation() {
|
||||||
|
MultivaluedMap<String, String> formData = createFormData("john", "password");
|
||||||
|
|
||||||
|
// Authenticate user with realm1
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm1, formData));
|
||||||
|
|
||||||
|
// Verify that user doesn't exists in realm2 and can't authenticate here
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm2, formData));
|
||||||
|
Assert.assertNull(realm2.getUser("john"));
|
||||||
|
|
||||||
|
// Add externalModel authenticationProvider into realm2 and point to realm1
|
||||||
|
setupAuthenticationProviders();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// this is needed for externalModel provider
|
||||||
|
ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
|
||||||
|
|
||||||
|
// Authenticate john in realm2 and verify that now he exists here.
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
|
||||||
|
UserModel john2 = realm2.getUser("john");
|
||||||
|
Assert.assertNotNull(john2);
|
||||||
|
Assert.assertEquals("john", john2.getLoginName());
|
||||||
|
Assert.assertEquals("John", john2.getFirstName());
|
||||||
|
Assert.assertEquals("Doe", john2.getLastName());
|
||||||
|
Assert.assertEquals("john@email.org", john2.getEmail());
|
||||||
|
|
||||||
|
// Verify link exists
|
||||||
|
Set<AuthenticationLinkModel> authLinks = realm2.getAuthenticationLinks(john2);
|
||||||
|
Assert.assertEquals(1, authLinks.size());
|
||||||
|
AuthenticationLinkModel authLink = authLinks.iterator().next();
|
||||||
|
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL);
|
||||||
|
} finally {
|
||||||
|
ResteasyProviderFactory.clearContextData();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExternalModelPasswordUpdate() {
|
||||||
|
// Add externalModel authenticationProvider into realm2 and point to realm1
|
||||||
|
setupAuthenticationProviders();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// this is needed for externalModel provider
|
||||||
|
ResteasyProviderFactory.pushContext(KeycloakSession.class, identitySession);
|
||||||
|
|
||||||
|
// Change credential via realm2 and validate that they are changed in both realms
|
||||||
|
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm2);
|
||||||
|
try {
|
||||||
|
authProviderManager.updatePassword("john", "password-updated");
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
ape.printStackTrace();
|
||||||
|
Assert.fail("Error not expected");
|
||||||
|
}
|
||||||
|
MultivaluedMap<String, String> formData = createFormData("john", "password-updated");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm1, formData));
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
|
||||||
|
|
||||||
|
|
||||||
|
// Switch to disallow password update propagation to realm1
|
||||||
|
setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm2);
|
||||||
|
|
||||||
|
// Change credential and validate that password is updated just for realm2
|
||||||
|
try {
|
||||||
|
authProviderManager.updatePassword("john", "password-updated2");
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
ape.printStackTrace();
|
||||||
|
Assert.fail("Error not expected");
|
||||||
|
}
|
||||||
|
formData = createFormData("john", "password-updated2");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm1, formData));
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm2, formData));
|
||||||
|
|
||||||
|
|
||||||
|
// Allow passwordUpdate propagation again
|
||||||
|
setPasswordUpdateForProvider(true, AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, realm2);
|
||||||
|
|
||||||
|
// Set passwordPolicy for realm1 and verify that password update fail
|
||||||
|
realm1.setPasswordPolicy(new PasswordPolicy("length(8)"));
|
||||||
|
try {
|
||||||
|
authProviderManager.updatePassword("john", "passw");
|
||||||
|
Assert.fail("Update not expected to pass");
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
ResteasyProviderFactory.clearContextData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup authentication providers into realm2
|
||||||
|
*/
|
||||||
|
private void setupAuthenticationProviders() {
|
||||||
|
AuthenticationProviderModel ap1 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
|
||||||
|
Map<String,String> config = new HashMap<String,String>();
|
||||||
|
config.put(AuthProviderConstants.EXTERNAL_REALM_ID, "realm1");
|
||||||
|
AuthenticationProviderModel ap2 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, true, config);
|
||||||
|
realm2.setAuthenticationProviders(Arrays.asList(ap1, ap2));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setPasswordUpdateForProvider(boolean isPasswordUpdate, String providerName, RealmModel realm) {
|
||||||
|
List<AuthenticationProviderModel> authProviders = realm.getAuthenticationProviders();
|
||||||
|
for (AuthenticationProviderModel authProvider : authProviders) {
|
||||||
|
if (providerName.equals(authProvider.getProviderName())) {
|
||||||
|
authProvider.setPasswordUpdateSupported(isPasswordUpdate);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
realm.setAuthenticationProviders(authProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MultivaluedMap<String, String> createFormData(String username, String password) {
|
||||||
|
MultivaluedMap<String, String> formData = new MultivaluedHashMap<String, String>();
|
||||||
|
formData.add("username", username);
|
||||||
|
formData.add(CredentialRepresentation.PASSWORD, password);
|
||||||
|
return formData;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,152 @@
|
||||||
|
package org.keycloak.model.test;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.MultivaluedMap;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.managers.AuthenticationManager;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
|
import org.keycloak.util.KeycloakRegistry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthProvidersLDAPTest extends AbstractModelTest {
|
||||||
|
|
||||||
|
private RealmModel realm;
|
||||||
|
private AuthenticationManager am;
|
||||||
|
private LDAPEmbeddedServer embeddedServer;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
@Override
|
||||||
|
public void before() throws Exception {
|
||||||
|
super.before();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.embeddedServer = new LDAPEmbeddedServer();
|
||||||
|
this.embeddedServer.setup();
|
||||||
|
this.embeddedServer.importLDIF("ldap/users.ldif");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error starting Embedded LDAP server.", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create realm and configure ldap
|
||||||
|
realm = realmManager.createRealm("realm");
|
||||||
|
realm.addRequiredCredential(CredentialRepresentation.PASSWORD);
|
||||||
|
this.embeddedServer.setupLdapInRealm(realm);
|
||||||
|
|
||||||
|
am = new AuthenticationManager();
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
@Override
|
||||||
|
public void after() throws Exception {
|
||||||
|
super.after();
|
||||||
|
try {
|
||||||
|
this.embeddedServer.tearDown();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error starting Embedded LDAP server.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLdapPasswordValidation() {
|
||||||
|
MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password");
|
||||||
|
|
||||||
|
// Verify that user doesn't exists in realm2 and can't authenticate here
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm, formData));
|
||||||
|
Assert.assertNull(realm.getUser("john"));
|
||||||
|
|
||||||
|
// Add ldap authenticationProvider
|
||||||
|
setupAuthenticationProviders();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// this is needed for Picketlink model provider
|
||||||
|
ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
|
||||||
|
|
||||||
|
// Authenticate john and verify that now he exists in realm
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
|
||||||
|
UserModel john = realm.getUser("john");
|
||||||
|
Assert.assertNotNull(john);
|
||||||
|
Assert.assertEquals("john", john.getLoginName());
|
||||||
|
Assert.assertEquals("John", john.getFirstName());
|
||||||
|
Assert.assertEquals("Doe", john.getLastName());
|
||||||
|
Assert.assertEquals("john@email.org", john.getEmail());
|
||||||
|
|
||||||
|
formData = AuthProvidersExternalModelTest.createFormData("john", "invalid");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm, formData));
|
||||||
|
|
||||||
|
// Verify link exists
|
||||||
|
Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(john);
|
||||||
|
Assert.assertEquals(1, authLinks.size());
|
||||||
|
AuthenticationLinkModel authLink = authLinks.iterator().next();
|
||||||
|
Assert.assertEquals(authLink.getAuthProvider(), AuthProviderConstants.PROVIDER_NAME_PICKETLINK);
|
||||||
|
} finally {
|
||||||
|
ResteasyProviderFactory.clearContextData();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testLdapPasswordUpdate() {
|
||||||
|
// Add ldap
|
||||||
|
setupAuthenticationProviders();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// this is needed for ldap provider
|
||||||
|
ResteasyProviderFactory.pushContext(KeycloakRegistry.class, new KeycloakRegistry());
|
||||||
|
|
||||||
|
// Change credential and validate that user can authenticate
|
||||||
|
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
|
||||||
|
try {
|
||||||
|
authProviderManager.updatePassword("john", "password-updated");
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
ape.printStackTrace();
|
||||||
|
Assert.fail("Error not expected");
|
||||||
|
}
|
||||||
|
MultivaluedMap<String, String> formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.SUCCESS, am.authenticateForm(realm, formData));
|
||||||
|
|
||||||
|
// Password updated just in LDAP, so validating directly in realm should fail
|
||||||
|
Assert.assertFalse(realm.validatePassword(realm.getUser("john"), "password-updated"));
|
||||||
|
|
||||||
|
// Switch to not allow updating passwords in ldap
|
||||||
|
AuthProvidersExternalModelTest.setPasswordUpdateForProvider(false, AuthProviderConstants.PROVIDER_NAME_PICKETLINK, realm);
|
||||||
|
|
||||||
|
// Change credential and validate that password is not updated
|
||||||
|
try {
|
||||||
|
authProviderManager.updatePassword("john", "password-updated2");
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
ape.printStackTrace();
|
||||||
|
Assert.fail("Error not expected");
|
||||||
|
}
|
||||||
|
formData = AuthProvidersExternalModelTest.createFormData("john", "password-updated2");
|
||||||
|
Assert.assertEquals(AuthenticationManager.AuthenticationStatus.INVALID_CREDENTIALS, am.authenticateForm(realm, formData));
|
||||||
|
} finally {
|
||||||
|
ResteasyProviderFactory.clearContextData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup authentication providers in realm
|
||||||
|
*/
|
||||||
|
private void setupAuthenticationProviders() {
|
||||||
|
AuthenticationProviderModel ap1 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
|
||||||
|
AuthenticationProviderModel ap2 = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
|
||||||
|
realm.setAuthenticationProviders(Arrays.asList(ap1, ap2));
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authForm() {
|
public void authForm() {
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
|
Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,15 +35,23 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
formData.remove(CredentialRepresentation.PASSWORD);
|
formData.remove(CredentialRepresentation.PASSWORD);
|
||||||
formData.add(CredentialRepresentation.PASSWORD, "invalid");
|
formData.add(CredentialRepresentation.PASSWORD, "invalid");
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
|
Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authFormMissingUsername() {
|
||||||
|
formData.remove("username");
|
||||||
|
|
||||||
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
|
Assert.assertEquals(AuthenticationStatus.INVALID_USER, status);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authFormMissingPassword() {
|
public void authFormMissingPassword() {
|
||||||
formData.remove(CredentialRepresentation.PASSWORD);
|
formData.remove(CredentialRepresentation.PASSWORD);
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.MISSING_PASSWORD, status);
|
Assert.assertEquals(AuthenticationStatus.MISSING_PASSWORD, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +60,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
realm.addRequiredCredential(CredentialRepresentation.TOTP);
|
realm.addRequiredCredential(CredentialRepresentation.TOTP);
|
||||||
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
user.addRequiredAction(RequiredAction.CONFIGURE_TOTP);
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.ACTIONS_REQUIRED, status);
|
Assert.assertEquals(AuthenticationStatus.ACTIONS_REQUIRED, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,7 +68,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
public void authFormUserDisabled() {
|
public void authFormUserDisabled() {
|
||||||
user.setEnabled(false);
|
user.setEnabled(false);
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.ACCOUNT_DISABLED, status);
|
Assert.assertEquals(AuthenticationStatus.ACCOUNT_DISABLED, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -82,7 +90,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
|
|
||||||
formData.add(CredentialRepresentation.TOTP, token);
|
formData.add(CredentialRepresentation.TOTP, token);
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
|
Assert.assertEquals(AuthenticationStatus.SUCCESS, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -93,7 +101,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
formData.remove(CredentialRepresentation.PASSWORD);
|
formData.remove(CredentialRepresentation.PASSWORD);
|
||||||
formData.add(CredentialRepresentation.PASSWORD, "invalid");
|
formData.add(CredentialRepresentation.PASSWORD, "invalid");
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
|
Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,7 +112,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
formData.remove(CredentialRepresentation.TOTP);
|
formData.remove(CredentialRepresentation.TOTP);
|
||||||
formData.add(CredentialRepresentation.TOTP, "invalid");
|
formData.add(CredentialRepresentation.TOTP, "invalid");
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
|
Assert.assertEquals(AuthenticationStatus.INVALID_CREDENTIALS, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -114,11 +122,12 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
|
|
||||||
formData.remove(CredentialRepresentation.TOTP);
|
formData.remove(CredentialRepresentation.TOTP);
|
||||||
|
|
||||||
AuthenticationStatus status = am.authenticateForm(realm, user, formData);
|
AuthenticationStatus status = am.authenticateForm(realm, formData);
|
||||||
Assert.assertEquals(AuthenticationStatus.MISSING_TOTP, status);
|
Assert.assertEquals(AuthenticationStatus.MISSING_TOTP, status);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@Override
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
super.before();
|
super.before();
|
||||||
realm = realmManager.createRealm("Test");
|
realm = realmManager.createRealm("Test");
|
||||||
|
@ -142,6 +151,7 @@ public class AuthenticationManagerTest extends AbstractModelTest {
|
||||||
realm.updateCredential(user, credential);
|
realm.updateCredential(user, credential);
|
||||||
|
|
||||||
formData = new MultivaluedHashMap<String, String>();
|
formData = new MultivaluedHashMap<String, String>();
|
||||||
|
formData.add("username", "test");
|
||||||
formData.add(CredentialRepresentation.PASSWORD, "password");
|
formData.add(CredentialRepresentation.PASSWORD, "password");
|
||||||
|
|
||||||
otp = new TimeBasedOTP();
|
otp = new TimeBasedOTP();
|
||||||
|
|
|
@ -19,6 +19,7 @@ import org.keycloak.services.managers.RealmManager;
|
||||||
public class CompositeRolesModelTest extends AbstractModelTest {
|
public class CompositeRolesModelTest extends AbstractModelTest {
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@Override
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
super.before();
|
super.before();
|
||||||
RealmManager manager = realmManager;
|
RealmManager manager = realmManager;
|
||||||
|
|
|
@ -6,9 +6,10 @@ import org.junit.Test;
|
||||||
import org.junit.runners.MethodSorters;
|
import org.junit.runners.MethodSorters;
|
||||||
import org.keycloak.models.AccountRoles;
|
import org.keycloak.models.AccountRoles;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
import org.keycloak.models.OAuthClientModel;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
|
@ -16,6 +17,7 @@ import org.keycloak.models.SocialLinkModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.services.managers.RealmManager;
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
@ -171,6 +173,60 @@ public class ImportTest extends AbstractModelTest {
|
||||||
Assert.assertNull(realm.getSocialLink(socialUser, "facebook"));
|
Assert.assertNull(realm.getSocialLink(socialUser, "facebook"));
|
||||||
Assert.assertFalse(realm.removeSocialLink(socialUser, "facebook"));
|
Assert.assertFalse(realm.removeSocialLink(socialUser, "facebook"));
|
||||||
|
|
||||||
|
// Test smtp config
|
||||||
|
Map<String, String> smtpConfig = realm.getSmtpConfig();
|
||||||
|
Assert.assertTrue(smtpConfig.size() == 3);
|
||||||
|
Assert.assertEquals("auto@keycloak.org", smtpConfig.get("from"));
|
||||||
|
Assert.assertEquals("localhost", smtpConfig.get("host"));
|
||||||
|
Assert.assertEquals("3025", smtpConfig.get("port"));
|
||||||
|
|
||||||
|
// Test social config
|
||||||
|
Map<String, String> socialConfig = realm.getSocialConfig();
|
||||||
|
Assert.assertTrue(socialConfig.size() == 2);
|
||||||
|
Assert.assertEquals("abc", socialConfig.get("google.key"));
|
||||||
|
Assert.assertEquals("def", socialConfig.get("google.secret"));
|
||||||
|
|
||||||
|
// Test ldap config
|
||||||
|
Map<String, String> ldapConfig = realm.getLdapServerConfig();
|
||||||
|
Assert.assertTrue(ldapConfig.size() == 5);
|
||||||
|
Assert.assertEquals("ldap://localhost:10389", ldapConfig.get("connectionUrl"));
|
||||||
|
Assert.assertEquals("dc=keycloak,dc=org", ldapConfig.get("baseDn"));
|
||||||
|
Assert.assertEquals("ou=People,dc=keycloak,dc=org", ldapConfig.get("userDnSuffix"));
|
||||||
|
|
||||||
|
// Test authentication providers
|
||||||
|
List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders();
|
||||||
|
Assert.assertTrue(authProviderModels.size() == 3);
|
||||||
|
AuthenticationProviderModel authProv1 = authProviderModels.get(0);
|
||||||
|
AuthenticationProviderModel authProv2 = authProviderModels.get(1);
|
||||||
|
AuthenticationProviderModel authProv3 = authProviderModels.get(2);
|
||||||
|
Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_MODEL, authProv1.getProviderName());
|
||||||
|
Assert.assertTrue(authProv1.isPasswordUpdateSupported());
|
||||||
|
Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, authProv2.getProviderName());
|
||||||
|
Assert.assertFalse(authProv2.isPasswordUpdateSupported());
|
||||||
|
Assert.assertEquals("trustedRealm", authProv2.getConfig().get("externalRealmId"));
|
||||||
|
Assert.assertEquals(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, authProv3.getProviderName());
|
||||||
|
Assert.assertTrue(authProv3.isPasswordUpdateSupported());
|
||||||
|
|
||||||
|
// Test authentication linking
|
||||||
|
Set<AuthenticationLinkModel> authLinks = realm.getAuthenticationLinks(socialUser);
|
||||||
|
Assert.assertEquals(2, authLinks.size());
|
||||||
|
boolean plFound = false;
|
||||||
|
boolean extFound = false;
|
||||||
|
for (AuthenticationLinkModel authLinkModel : authLinks) {
|
||||||
|
if (AuthProviderConstants.PROVIDER_NAME_PICKETLINK.equals(authLinkModel.getAuthProvider())) {
|
||||||
|
plFound = true;
|
||||||
|
Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser1");
|
||||||
|
} else if (AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL.equals(authLinkModel.getAuthProvider())) {
|
||||||
|
extFound = true;
|
||||||
|
Assert.assertEquals(authLinkModel.getAuthUserId(), "myUser11");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Assert.assertTrue(plFound && extFound);
|
||||||
|
|
||||||
|
UserModel foundAuthUser = realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "myUser1"));
|
||||||
|
Assert.assertEquals(foundAuthUser.getLoginName(), socialUser.getLoginName());
|
||||||
|
Assert.assertNull(realm.getUserByAuthenticationLink(new AuthenticationLinkModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, "not-existing")));
|
||||||
|
|
||||||
commit();
|
commit();
|
||||||
|
|
||||||
realm = realmManager.getRealm("demo");
|
realm = realmManager.getRealm("demo");
|
||||||
|
|
|
@ -19,6 +19,7 @@ public class MultipleRealmsTest extends AbstractModelTest {
|
||||||
private RealmModel realm2;
|
private RealmModel realm2;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@Override
|
||||||
public void before() throws Exception {
|
public void before() throws Exception {
|
||||||
super.before();
|
super.before();
|
||||||
realm1 = identitySession.createRealm("id1", "realm1");
|
realm1 = identitySession.createRealm("id1", "realm1");
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
idm.test.ldap.connection.url=ldap\://localhost\:10389
|
||||||
|
idm.test.ldap.base.dn=dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.group.dn.suffix=ou\=Groups,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.user.dn.suffix=ou\=People,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.agent.dn.suffix=ou\=Agent,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.start.embedded.ldap.server=true
|
||||||
|
idm.test.ldap.bind.dn=uid\=admin,ou\=system
|
||||||
|
idm.test.ldap.bind.credential=secret
|
31
model/tests/src/test/resources/ldap/users.ldif
Normal file
31
model/tests/src/test/resources/ldap/users.ldif
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
dn: dc=keycloak,dc=org
|
||||||
|
objectclass: dcObject
|
||||||
|
objectclass: organization
|
||||||
|
o: Keycloak
|
||||||
|
dc: Keycloak
|
||||||
|
|
||||||
|
dn: ou=People,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: organizationalUnit
|
||||||
|
ou: People
|
||||||
|
|
||||||
|
dn: ou=Roles,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: organizationalUnit
|
||||||
|
ou: Roles
|
||||||
|
|
||||||
|
dn: ou=Groups,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: organizationalUnit
|
||||||
|
ou: Groups
|
||||||
|
|
||||||
|
dn: uid=john,ou=People,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: uidObject
|
||||||
|
objectclass: person
|
||||||
|
objectclass: inetOrgPerson
|
||||||
|
uid: john
|
||||||
|
cn: John
|
||||||
|
sn: Doe
|
||||||
|
mail: john@email.org
|
||||||
|
userPassword: password
|
|
@ -7,6 +7,38 @@
|
||||||
"requiredCredentials": [ "password" ],
|
"requiredCredentials": [ "password" ],
|
||||||
"defaultRoles": [ "foo", "bar" ],
|
"defaultRoles": [ "foo", "bar" ],
|
||||||
"verifyEmail" : "true",
|
"verifyEmail" : "true",
|
||||||
|
"smtpServer": {
|
||||||
|
"from": "auto@keycloak.org",
|
||||||
|
"host": "localhost",
|
||||||
|
"port":"3025"
|
||||||
|
},
|
||||||
|
"ldapServer": {
|
||||||
|
"connectionUrl": "ldap://localhost:10389",
|
||||||
|
"baseDn": "dc=keycloak,dc=org",
|
||||||
|
"userDnSuffix": "ou=People,dc=keycloak,dc=org",
|
||||||
|
"bindDn": "uid=admin,ou=system",
|
||||||
|
"bindCredential": "secret"
|
||||||
|
},
|
||||||
|
"socialProviders": {
|
||||||
|
"google.key": "abc",
|
||||||
|
"google.secret": "def"
|
||||||
|
},
|
||||||
|
"authenticationProviders": [
|
||||||
|
{
|
||||||
|
"providerName": "model"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"providerName": "externalModel",
|
||||||
|
"passwordUpdateSupported": false,
|
||||||
|
"config": {
|
||||||
|
"externalRealmId": "trustedRealm"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"providerName": "picketlink",
|
||||||
|
"passwordUpdateSupported": true
|
||||||
|
}
|
||||||
|
],
|
||||||
"users": [
|
"users": [
|
||||||
{
|
{
|
||||||
"username": "wburke",
|
"username": "wburke",
|
||||||
|
@ -68,6 +100,21 @@
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
"authenticationMappings": [
|
||||||
|
{
|
||||||
|
"username": "mySocialUser",
|
||||||
|
"authenticationLinks": [
|
||||||
|
{
|
||||||
|
"authProvider": "picketlink",
|
||||||
|
"authUserId": "myUser1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"authProvider": "externalModel",
|
||||||
|
"authUserId": "myUser11"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
"applications": [
|
"applications": [
|
||||||
{
|
{
|
||||||
"name": "Application",
|
"name": "Application",
|
||||||
|
|
24
pom.xml
24
pom.xml
|
@ -15,7 +15,8 @@
|
||||||
<keycloak.apache.httpcomponents.version>4.1.2</keycloak.apache.httpcomponents.version>
|
<keycloak.apache.httpcomponents.version>4.1.2</keycloak.apache.httpcomponents.version>
|
||||||
<resteasy.version>3.0.6.Final</resteasy.version>
|
<resteasy.version>3.0.6.Final</resteasy.version>
|
||||||
<undertow.version>1.0.0.Final</undertow.version>
|
<undertow.version>1.0.0.Final</undertow.version>
|
||||||
<picketlink.version>2.5.0.Beta6</picketlink.version>
|
<picketlink.version>2.6.0.CR1</picketlink.version>
|
||||||
|
<picketbox.ldap.version>1.0.2.Final</picketbox.ldap.version>
|
||||||
<mongo.driver.version>2.11.3</mongo.driver.version>
|
<mongo.driver.version>2.11.3</mongo.driver.version>
|
||||||
<jboss.logging.version>3.1.1.GA</jboss.logging.version>
|
<jboss.logging.version>3.1.1.GA</jboss.logging.version>
|
||||||
<jboss-logging-tools.version>1.2.0.Beta1</jboss-logging-tools.version>
|
<jboss-logging-tools.version>1.2.0.Beta1</jboss-logging-tools.version>
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
<dom4j.version>1.6.1</dom4j.version>
|
<dom4j.version>1.6.1</dom4j.version>
|
||||||
<xml-apis.version>1.4.01</xml-apis.version>
|
<xml-apis.version>1.4.01</xml-apis.version>
|
||||||
<mysql.version>5.1.25</mysql.version>
|
<mysql.version>5.1.25</mysql.version>
|
||||||
<slf4j.version>1.6.1</slf4j.version>
|
<slf4j.version>1.5.10</slf4j.version>
|
||||||
<jboss.version>7.1.1.Final</jboss.version>
|
<jboss.version>7.1.1.Final</jboss.version>
|
||||||
<wildfly.version>8.0.0.Final</wildfly.version>
|
<wildfly.version>8.0.0.Final</wildfly.version>
|
||||||
<json.version>20131018</json.version>
|
<json.version>20131018</json.version>
|
||||||
|
@ -91,6 +92,7 @@
|
||||||
<module>examples</module>
|
<module>examples</module>
|
||||||
<module>testsuite</module>
|
<module>testsuite</module>
|
||||||
<module>server</module>
|
<module>server</module>
|
||||||
|
<module>spi</module>
|
||||||
</modules>
|
</modules>
|
||||||
|
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
|
@ -215,6 +217,17 @@
|
||||||
<artifactId>picketlink-config</artifactId>
|
<artifactId>picketlink-config</artifactId>
|
||||||
<version>${picketlink.version}</version>
|
<version>${picketlink.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketbox</groupId>
|
||||||
|
<artifactId>picketbox-ldap</artifactId>
|
||||||
|
<version>${picketbox.ldap.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketbox</groupId>
|
||||||
|
<artifactId>picketbox-ldap</artifactId>
|
||||||
|
<version>${picketbox.ldap.version}</version>
|
||||||
|
<type>test-jar</type>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
@ -339,11 +352,18 @@
|
||||||
<artifactId>xml-apis</artifactId>
|
<artifactId>xml-apis</artifactId>
|
||||||
<version>${xml-apis.version}</version>
|
<version>${xml-apis.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!-- Older 1.5.10 binding required by embedded ApacheDS -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
<artifactId>slf4j-api</artifactId>
|
<artifactId>slf4j-api</artifactId>
|
||||||
<version>${slf4j.version}</version>
|
<version>${slf4j.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
<version>${slf4j.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Needed for picketlink perf test -->
|
<!-- Needed for picketlink perf test -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>mysql</groupId>
|
<groupId>mysql</groupId>
|
||||||
|
|
|
@ -9,9 +9,11 @@
|
||||||
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||||
|
|
|
@ -62,6 +62,12 @@
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-spi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
<artifactId>jboss-logging</artifactId>
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
@ -139,6 +145,12 @@
|
||||||
<groupId>com.icegreen</groupId>
|
<groupId>com.icegreen</groupId>
|
||||||
<artifactId>greenmail</artifactId>
|
<artifactId>greenmail</artifactId>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.models.KeycloakSession;
|
import org.keycloak.models.KeycloakSession;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.KeycloakTransaction;
|
import org.keycloak.models.KeycloakTransaction;
|
||||||
|
import org.keycloak.util.KeycloakRegistry;
|
||||||
|
|
||||||
import javax.servlet.Filter;
|
import javax.servlet.Filter;
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
|
@ -25,7 +26,9 @@ public class KeycloakSessionServletFilter implements Filter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
|
||||||
KeycloakSessionFactory factory = (KeycloakSessionFactory)servletRequest.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
|
KeycloakRegistry registry = (KeycloakRegistry)servletRequest.getServletContext().getAttribute(KeycloakRegistry.class.getName());
|
||||||
|
ResteasyProviderFactory.pushContext(KeycloakRegistry.class, registry);
|
||||||
|
KeycloakSessionFactory factory = registry.getService(KeycloakSessionFactory.class);
|
||||||
if (factory == null) throw new ServletException("Factory was null");
|
if (factory == null) throw new ServletException("Factory was null");
|
||||||
KeycloakSession session = factory.createSession();
|
KeycloakSession session = factory.createSession();
|
||||||
ResteasyProviderFactory.pushContext(KeycloakSession.class, session);
|
ResteasyProviderFactory.pushContext(KeycloakSession.class, session);
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.services.listeners;
|
package org.keycloak.services.listeners;
|
||||||
|
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
|
import org.keycloak.util.KeycloakRegistry;
|
||||||
|
|
||||||
import javax.servlet.ServletContextEvent;
|
import javax.servlet.ServletContextEvent;
|
||||||
import javax.servlet.ServletContextListener;
|
import javax.servlet.ServletContextListener;
|
||||||
|
@ -16,7 +17,8 @@ public class KeycloakSessionDestroyListener implements ServletContextListener {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void contextDestroyed(ServletContextEvent sce) {
|
public void contextDestroyed(ServletContextEvent sce) {
|
||||||
KeycloakSessionFactory factory = (KeycloakSessionFactory) sce.getServletContext().getAttribute(KeycloakSessionFactory.class.getName());
|
KeycloakRegistry registry = (KeycloakRegistry)sce.getServletContext().getAttribute(KeycloakRegistry.class.getName());
|
||||||
|
KeycloakSessionFactory factory = registry.getService(KeycloakSessionFactory.class);
|
||||||
if (factory != null) {
|
if (factory != null) {
|
||||||
factory.close();
|
factory.close();
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,16 +6,19 @@ import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
import org.keycloak.RSATokenVerifier;
|
import org.keycloak.RSATokenVerifier;
|
||||||
import org.keycloak.VerificationException;
|
import org.keycloak.VerificationException;
|
||||||
import org.keycloak.jose.jws.JWSBuilder;
|
import org.keycloak.jose.jws.JWSBuilder;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
|
||||||
import org.keycloak.models.RealmModel;
|
import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.services.resources.RealmsResource;
|
import org.keycloak.services.resources.RealmsResource;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
|
import org.keycloak.spi.authentication.AuthResult;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticatedUser;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import javax.ws.rs.core.Cookie;
|
import javax.ws.rs.core.Cookie;
|
||||||
|
@ -25,7 +28,6 @@ import javax.ws.rs.core.NewCookie;
|
||||||
import javax.ws.rs.core.UriInfo;
|
import javax.ws.rs.core.UriInfo;
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -178,16 +180,14 @@ public class AuthenticationManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticationStatus authenticateForm(RealmModel realm, UserModel user, MultivaluedMap<String, String> formData) {
|
public AuthenticationStatus authenticateForm(RealmModel realm, MultivaluedMap<String, String> formData) {
|
||||||
if (user == null) {
|
String username = formData.getFirst(FORM_USERNAME);
|
||||||
logger.debug("Not Authenticated! Incorrect user name");
|
if (username == null) {
|
||||||
|
logger.warn("Username not provided");
|
||||||
return AuthenticationStatus.INVALID_USER;
|
return AuthenticationStatus.INVALID_USER;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.isEnabled()) {
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||||
logger.debug("Account is disabled, contact admin. " + user.getLoginName());
|
|
||||||
return AuthenticationStatus.ACCOUNT_DISABLED;
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<String> types = new HashSet<String>();
|
Set<String> types = new HashSet<String>();
|
||||||
|
|
||||||
|
@ -202,22 +202,71 @@ public class AuthenticationManager {
|
||||||
return AuthenticationStatus.MISSING_PASSWORD;
|
return AuthenticationStatus.MISSING_PASSWORD;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.isTotp()) {
|
if (user == null && types.contains(CredentialRepresentation.TOTP)) {
|
||||||
|
logger.warn("User doesn't exists and TOTP is required for the realm");
|
||||||
|
return AuthenticationStatus.INVALID_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user != null && user.isTotp()) {
|
||||||
String token = formData.getFirst(CredentialRepresentation.TOTP);
|
String token = formData.getFirst(CredentialRepresentation.TOTP);
|
||||||
if (token == null) {
|
if (token == null) {
|
||||||
logger.warn("TOTP token not provided");
|
logger.warn("TOTP token not provided");
|
||||||
return AuthenticationStatus.MISSING_TOTP;
|
return AuthenticationStatus.MISSING_TOTP;
|
||||||
}
|
}
|
||||||
|
if (!checkEnabled(user)) {
|
||||||
|
return AuthenticationStatus.ACCOUNT_DISABLED;
|
||||||
|
}
|
||||||
logger.debug("validating TOTP");
|
logger.debug("validating TOTP");
|
||||||
if (!realm.validateTOTP(user, password, token)) {
|
if (!realm.validateTOTP(user, password, token)) {
|
||||||
return AuthenticationStatus.INVALID_CREDENTIALS;
|
return AuthenticationStatus.INVALID_CREDENTIALS;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
logger.debug("validating password for user: " + user.getLoginName());
|
logger.debug("validating password for user: " + username);
|
||||||
if (!realm.validatePassword(user, password)) {
|
|
||||||
logger.debug("invalid password for user: " + user.getLoginName());
|
AuthResult authResult = AuthenticationProviderManager.getManager(realm).validatePassword(username, password);
|
||||||
|
if (authResult.getAuthProviderStatus() == AuthProviderStatus.FAILED) {
|
||||||
|
logger.debug("invalid password for user: " + username);
|
||||||
return AuthenticationStatus.INVALID_CREDENTIALS;
|
return AuthenticationStatus.INVALID_CREDENTIALS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (authResult.getAuthenticatedUser() != null) {
|
||||||
|
AuthenticatedUser authUser = authResult.getAuthenticatedUser();
|
||||||
|
AuthenticationLinkModel authLink = new AuthenticationLinkModel(authResult.getProviderName(), authUser.getId());
|
||||||
|
user = realm.getUserByAuthenticationLink(authLink);
|
||||||
|
if (user == null) {
|
||||||
|
// Create new user, which has been successfully authenticated and link him with authentication provider
|
||||||
|
user = realm.addUser(authUser.getUsername());
|
||||||
|
user.setEnabled(true);
|
||||||
|
user.setFirstName(authUser.getFirstName());
|
||||||
|
user.setLastName(authUser.getLastName());
|
||||||
|
user.setEmail(authUser.getEmail());
|
||||||
|
|
||||||
|
realm.addAuthenticationLink(user, authLink);
|
||||||
|
logger.info("User " + username + " successfully authenticated and created based on provider " + authResult.getProviderName());
|
||||||
|
} else {
|
||||||
|
// Existing user has been authenticated
|
||||||
|
if (!checkEnabled(user)) {
|
||||||
|
return AuthenticationStatus.ACCOUNT_DISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Update of existing account?
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authenticated username could be different from the "form" username. In this case, we will change it
|
||||||
|
if (!username.equals(user.getLoginName())) {
|
||||||
|
formData.putSingle(FORM_USERNAME, user.getLoginName());
|
||||||
|
logger.debug("Existing user " + user.getLoginName() + " successfully authenticated");
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Authentication provider didn't send AuthenticatedUser. Using already retrieved user based on username from "form"
|
||||||
|
if (user == null) {
|
||||||
|
logger.warn("User '" + username + "' successfully authenticated, but he doesn't exists and don't know how to create him");
|
||||||
|
return AuthenticationStatus.INVALID_USER;
|
||||||
|
} else if (!checkEnabled(user)) {
|
||||||
|
return AuthenticationStatus.ACCOUNT_DISABLED;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!user.getRequiredActions().isEmpty()) {
|
if (!user.getRequiredActions().isEmpty()) {
|
||||||
|
@ -242,6 +291,15 @@ public class AuthenticationManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkEnabled(UserModel user) {
|
||||||
|
if (!user.isEnabled()) {
|
||||||
|
logger.warn("Account is disabled, contact admin. " + user.getLoginName());
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public enum AuthenticationStatus {
|
public enum AuthenticationStatus {
|
||||||
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
|
SUCCESS, ACCOUNT_DISABLED, ACTIONS_REQUIRED, INVALID_USER, INVALID_CREDENTIALS, MISSING_PASSWORD, MISSING_TOTP, FAILED
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package org.keycloak.services.managers;
|
package org.keycloak.services.managers;
|
||||||
|
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.ClaimMask;
|
import org.keycloak.models.ClaimMask;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -9,6 +10,7 @@ import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.RoleModel;
|
import org.keycloak.models.RoleModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.ClaimRepresentation;
|
import org.keycloak.representations.idm.ClaimRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -83,6 +85,7 @@ public class ModelToRepresentation {
|
||||||
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
rep.setAccessCodeLifespanUserAction(realm.getAccessCodeLifespanUserAction());
|
||||||
rep.setSmtpServer(realm.getSmtpConfig());
|
rep.setSmtpServer(realm.getSmtpConfig());
|
||||||
rep.setSocialProviders(realm.getSocialConfig());
|
rep.setSocialProviders(realm.getSocialConfig());
|
||||||
|
rep.setLdapServer(realm.getLdapServerConfig());
|
||||||
rep.setAccountTheme(realm.getAccountTheme());
|
rep.setAccountTheme(realm.getAccountTheme());
|
||||||
rep.setLoginTheme(realm.getLoginTheme());
|
rep.setLoginTheme(realm.getLoginTheme());
|
||||||
if (realm.getPasswordPolicy() != null) {
|
if (realm.getPasswordPolicy() != null) {
|
||||||
|
@ -105,6 +108,19 @@ public class ModelToRepresentation {
|
||||||
rep.getRequiredCredentials().add(cred.getType());
|
rep.getRequiredCredentials().add(cred.getType());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<AuthenticationProviderModel> authProviderModels = realm.getAuthenticationProviders();
|
||||||
|
if (authProviderModels.size() > 0) {
|
||||||
|
List<AuthenticationProviderRepresentation> authProviderReps = new ArrayList<AuthenticationProviderRepresentation>();
|
||||||
|
for (AuthenticationProviderModel model : authProviderModels) {
|
||||||
|
AuthenticationProviderRepresentation authProvRep = new AuthenticationProviderRepresentation();
|
||||||
|
authProvRep.setProviderName(model.getProviderName());
|
||||||
|
authProvRep.setPasswordUpdateSupported(model.isPasswordUpdateSupported());
|
||||||
|
authProvRep.setConfig(model.getConfig());
|
||||||
|
authProviderReps.add(authProvRep);
|
||||||
|
}
|
||||||
|
rep.setAuthenticationProviders(authProviderReps);
|
||||||
|
}
|
||||||
return rep;
|
return rep;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,8 @@ import org.jboss.resteasy.logging.Logger;
|
||||||
import org.keycloak.models.AccountRoles;
|
import org.keycloak.models.AccountRoles;
|
||||||
import org.keycloak.models.AdminRoles;
|
import org.keycloak.models.AdminRoles;
|
||||||
import org.keycloak.models.ApplicationModel;
|
import org.keycloak.models.ApplicationModel;
|
||||||
|
import org.keycloak.models.AuthenticationLinkModel;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
import org.keycloak.models.ClientModel;
|
import org.keycloak.models.ClientModel;
|
||||||
import org.keycloak.models.Config;
|
import org.keycloak.models.Config;
|
||||||
import org.keycloak.models.Constants;
|
import org.keycloak.models.Constants;
|
||||||
|
@ -18,6 +20,9 @@ import org.keycloak.models.UserModel;
|
||||||
import org.keycloak.models.UserModel.RequiredAction;
|
import org.keycloak.models.UserModel.RequiredAction;
|
||||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.idm.ApplicationRepresentation;
|
import org.keycloak.representations.idm.ApplicationRepresentation;
|
||||||
|
import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
|
||||||
|
import org.keycloak.representations.idm.AuthenticationMappingRepresentation;
|
||||||
|
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
import org.keycloak.representations.idm.OAuthClientRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
|
@ -31,6 +36,7 @@ import org.keycloak.representations.idm.UserRoleMappingRepresentation;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -145,6 +151,14 @@ public class RealmManager {
|
||||||
realm.setSocialConfig(new HashMap(rep.getSocialProviders()));
|
realm.setSocialConfig(new HashMap(rep.getSocialProviders()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (rep.getLdapServer() != null) {
|
||||||
|
realm.setLdapServerConfig(new HashMap(rep.getLdapServer()));
|
||||||
|
}
|
||||||
|
if (rep.getAuthenticationProviders() != null) {
|
||||||
|
List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
|
||||||
|
realm.setAuthenticationProviders(authProviderModels);
|
||||||
|
}
|
||||||
|
|
||||||
if ("GENERATE".equals(rep.getPublicKey())) {
|
if ("GENERATE".equals(rep.getPublicKey())) {
|
||||||
generateRealmKeys(realm);
|
generateRealmKeys(realm);
|
||||||
}
|
}
|
||||||
|
@ -371,6 +385,15 @@ public class RealmManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (rep.getAuthenticationMappings() != null) {
|
||||||
|
for (AuthenticationMappingRepresentation authMapping : rep.getAuthenticationMappings()) {
|
||||||
|
UserModel user = userMap.get(authMapping.getUsername());
|
||||||
|
for (AuthenticationLinkRepresentation link : authMapping.getAuthenticationLinks()) {
|
||||||
|
AuthenticationLinkModel mappingModel = new AuthenticationLinkModel(link.getAuthProvider(), link.getAuthUserId());
|
||||||
|
newRealm.addAuthenticationLink(user, mappingModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (rep.getSmtpServer() != null) {
|
if (rep.getSmtpServer() != null) {
|
||||||
newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
|
newRealm.setSmtpConfig(new HashMap(rep.getSmtpServer()));
|
||||||
|
@ -379,6 +402,14 @@ public class RealmManager {
|
||||||
if (rep.getSocialProviders() != null) {
|
if (rep.getSocialProviders() != null) {
|
||||||
newRealm.setSocialConfig(new HashMap(rep.getSocialProviders()));
|
newRealm.setSocialConfig(new HashMap(rep.getSocialProviders()));
|
||||||
}
|
}
|
||||||
|
if (rep.getLdapServer() != null) {
|
||||||
|
newRealm.setLdapServerConfig(new HashMap(rep.getLdapServer()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (rep.getAuthenticationProviders() != null) {
|
||||||
|
List<AuthenticationProviderModel> authProviderModels = convertAuthenticationProviders(rep.getAuthenticationProviders());
|
||||||
|
newRealm.setAuthenticationProviders(authProviderModels);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) {
|
public void addComposites(RoleModel role, RoleRepresentation roleRep, RealmModel realm) {
|
||||||
|
@ -490,5 +521,16 @@ public class RealmManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected List<AuthenticationProviderModel> convertAuthenticationProviders(List<AuthenticationProviderRepresentation> authenticationProviders) {
|
||||||
|
List<AuthenticationProviderModel> result = new ArrayList<AuthenticationProviderModel>();
|
||||||
|
|
||||||
|
for (AuthenticationProviderRepresentation representation : authenticationProviders) {
|
||||||
|
AuthenticationProviderModel model = new AuthenticationProviderModel(representation.getProviderName(),
|
||||||
|
representation.isPasswordUpdateSupported(), representation.getConfig());
|
||||||
|
result.add(model);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -42,8 +42,10 @@ import org.keycloak.services.resources.flows.Urls;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
import org.keycloak.social.SocialLoader;
|
import org.keycloak.social.SocialLoader;
|
||||||
import org.keycloak.social.SocialProvider;
|
import org.keycloak.social.SocialProvider;
|
||||||
import org.keycloak.social.SocialProviderConfig;
|
|
||||||
import org.keycloak.social.SocialProviderException;
|
import org.keycloak.social.SocialProviderException;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
|
|
||||||
import javax.ws.rs.*;
|
import javax.ws.rs.*;
|
||||||
import javax.ws.rs.core.*;
|
import javax.ws.rs.core.*;
|
||||||
|
@ -237,23 +239,20 @@ public class AccountService {
|
||||||
return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
|
return account.setError(Messages.INVALID_PASSWORD_CONFIRM).createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AuthenticationProviderManager authProviderManager = AuthenticationProviderManager.getManager(realm);
|
||||||
if (Validation.isEmpty(password)) {
|
if (Validation.isEmpty(password)) {
|
||||||
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
return account.setError(Messages.MISSING_PASSWORD).createResponse(AccountPages.PASSWORD);
|
||||||
} else if (!realm.validatePassword(user, password)) {
|
// TODO: This may not work in some cases. For example if ldap username is "foo" but actual loginName of user is "bar", which could theoretically happen...
|
||||||
|
} else if (authProviderManager.validatePassword(user.getLoginName(), password).getAuthProviderStatus() != AuthProviderStatus.SUCCESS) {
|
||||||
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
return account.setError(Messages.INVALID_PASSWORD_EXISTING).createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
String error = Validation.validatePassword(formData, realm.getPasswordPolicy());
|
try {
|
||||||
if (error != null) {
|
authProviderManager.updatePassword(user.getLoginName(), passwordNew);
|
||||||
return account.setError(error).createResponse(AccountPages.PASSWORD);
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
return account.setError(ape.getMessage()).createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
|
||||||
credentials.setValue(passwordNew);
|
|
||||||
|
|
||||||
realm.updateCredential(user, credentials);
|
|
||||||
|
|
||||||
return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
|
return account.setSuccess("accountPasswordUpdated").createResponse(AccountPages.PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import org.jboss.resteasy.logging.Logger;
|
||||||
import org.keycloak.SkeletonKeyContextResolver;
|
import org.keycloak.SkeletonKeyContextResolver;
|
||||||
import org.keycloak.models.KeycloakSessionFactory;
|
import org.keycloak.models.KeycloakSessionFactory;
|
||||||
import org.keycloak.models.ModelProvider;
|
import org.keycloak.models.ModelProvider;
|
||||||
|
import org.keycloak.util.KeycloakRegistry;
|
||||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||||
import org.keycloak.services.managers.SocialRequestManager;
|
import org.keycloak.services.managers.SocialRequestManager;
|
||||||
import org.keycloak.services.managers.TokenManager;
|
import org.keycloak.services.managers.TokenManager;
|
||||||
|
@ -35,7 +36,9 @@ public class KeycloakApplication extends Application {
|
||||||
public KeycloakApplication(@Context ServletContext context) {
|
public KeycloakApplication(@Context ServletContext context) {
|
||||||
this.factory = createSessionFactory();
|
this.factory = createSessionFactory();
|
||||||
this.contextPath = context.getContextPath();
|
this.contextPath = context.getContextPath();
|
||||||
context.setAttribute(KeycloakSessionFactory.class.getName(), factory);
|
KeycloakRegistry registry = new KeycloakRegistry();
|
||||||
|
registry.putService(KeycloakSessionFactory.class, factory);
|
||||||
|
context.setAttribute(KeycloakRegistry.class.getName(), registry);
|
||||||
//classes.add(KeycloakSessionCleanupFilter.class);
|
//classes.add(KeycloakSessionCleanupFilter.class);
|
||||||
|
|
||||||
TokenManager tokenManager = new TokenManager();
|
TokenManager tokenManager = new TokenManager();
|
||||||
|
|
|
@ -42,6 +42,8 @@ import org.keycloak.services.managers.TokenManager;
|
||||||
import org.keycloak.services.messages.Messages;
|
import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
|
@ -171,17 +173,12 @@ public class RequiredActionsService {
|
||||||
return loginForms.setError(Messages.NOTMATCH_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
|
return loginForms.setError(Messages.NOTMATCH_PASSWORD).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
String error = realm.getPasswordPolicy().validate(passwordNew);
|
try {
|
||||||
if (error != null) {
|
AuthenticationProviderManager.getManager(realm).updatePassword(user.getLoginName(), passwordNew);
|
||||||
return loginForms.setError(error).createResponse(RequiredAction.UPDATE_PASSWORD);
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
return loginForms.setError(ape.getMessage()).createResponse(RequiredAction.UPDATE_PASSWORD);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
|
||||||
credentials.setValue(passwordNew);
|
|
||||||
|
|
||||||
realm.updateCredential(user, credentials);
|
|
||||||
|
|
||||||
logger.debug("updatePassword updated credential");
|
logger.debug("updatePassword updated credential");
|
||||||
|
|
||||||
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
user.removeRequiredAction(RequiredAction.UPDATE_PASSWORD);
|
||||||
|
|
|
@ -16,6 +16,7 @@ import org.keycloak.models.RealmModel;
|
||||||
import org.keycloak.models.RequiredCredentialModel;
|
import org.keycloak.models.RequiredCredentialModel;
|
||||||
import org.keycloak.models.UserCredentialModel;
|
import org.keycloak.models.UserCredentialModel;
|
||||||
import org.keycloak.models.UserModel;
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
import org.keycloak.representations.AccessToken;
|
import org.keycloak.representations.AccessToken;
|
||||||
import org.keycloak.representations.AccessTokenResponse;
|
import org.keycloak.representations.AccessTokenResponse;
|
||||||
import org.keycloak.representations.idm.CredentialRepresentation;
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
@ -28,6 +29,8 @@ import org.keycloak.services.messages.Messages;
|
||||||
import org.keycloak.services.resources.flows.Flows;
|
import org.keycloak.services.resources.flows.Flows;
|
||||||
import org.keycloak.services.resources.flows.OAuthFlows;
|
import org.keycloak.services.resources.flows.OAuthFlows;
|
||||||
import org.keycloak.services.validation.Validation;
|
import org.keycloak.services.validation.Validation;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderManager;
|
||||||
import org.keycloak.util.BasicAuthHelper;
|
import org.keycloak.util.BasicAuthHelper;
|
||||||
import org.keycloak.util.Time;
|
import org.keycloak.util.Time;
|
||||||
|
|
||||||
|
@ -37,7 +40,6 @@ import javax.ws.rs.ForbiddenException;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.HeaderParam;
|
import javax.ws.rs.HeaderParam;
|
||||||
import javax.ws.rs.NotAcceptableException;
|
import javax.ws.rs.NotAcceptableException;
|
||||||
import javax.ws.rs.NotAllowedException;
|
|
||||||
import javax.ws.rs.NotAuthorizedException;
|
import javax.ws.rs.NotAuthorizedException;
|
||||||
import javax.ws.rs.OPTIONS;
|
import javax.ws.rs.OPTIONS;
|
||||||
import javax.ws.rs.POST;
|
import javax.ws.rs.POST;
|
||||||
|
@ -149,23 +151,18 @@ public class TokenService {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
String username = form.getFirst(AuthenticationManager.FORM_USERNAME);
|
if (form.getFirst(AuthenticationManager.FORM_USERNAME) == null) {
|
||||||
if (username == null) {
|
throw new NotAuthorizedException("No username");
|
||||||
throw new NotAuthorizedException("No user");
|
|
||||||
}
|
}
|
||||||
if (!realm.isEnabled()) {
|
if (!realm.isEnabled()) {
|
||||||
throw new NotAuthorizedException("Disabled realm");
|
throw new NotAuthorizedException("Disabled realm");
|
||||||
}
|
}
|
||||||
UserModel user = realm.getUser(username);
|
|
||||||
if (user == null) {
|
if (authManager.authenticateForm(realm, form) != AuthenticationStatus.SUCCESS) {
|
||||||
throw new NotAuthorizedException("No user");
|
|
||||||
}
|
|
||||||
if (!user.isEnabled()) {
|
|
||||||
throw new NotAuthorizedException("Disabled user.");
|
|
||||||
}
|
|
||||||
if (authManager.authenticateForm(realm, user, form) != AuthenticationStatus.SUCCESS) {
|
|
||||||
throw new NotAuthorizedException("Auth failed");
|
throw new NotAuthorizedException("Auth failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserModel user = realm.getUser(form.getFirst(AuthenticationManager.FORM_USERNAME));
|
||||||
String scope = form.getFirst(OAuth2Constants.SCOPE);
|
String scope = form.getFirst(OAuth2Constants.SCOPE);
|
||||||
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
|
AccessTokenResponse res = tokenManager.responseBuilder(realm, client)
|
||||||
.generateAccessToken(scope, client, user)
|
.generateAccessToken(scope, client, user)
|
||||||
|
@ -237,17 +234,7 @@ public class TokenService {
|
||||||
return oauth.redirectError(client, "access_denied", state, redirect);
|
return oauth.redirectError(client, "access_denied", state, redirect);
|
||||||
}
|
}
|
||||||
|
|
||||||
String username = formData.getFirst("username");
|
AuthenticationStatus status = authManager.authenticateForm(realm, formData);
|
||||||
UserModel user = realm.getUser(username);
|
|
||||||
if (user == null && username.contains("@")) {
|
|
||||||
user = realm.getUserByEmail(username);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user == null) {
|
|
||||||
return Flows.forms(realm, request, uriInfo).setError(Messages.INVALID_USER).setFormData(formData).createLogin();
|
|
||||||
}
|
|
||||||
|
|
||||||
AuthenticationStatus status = authManager.authenticateForm(realm, user, formData);
|
|
||||||
|
|
||||||
String rememberMe = formData.getFirst("rememberMe");
|
String rememberMe = formData.getFirst("rememberMe");
|
||||||
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
boolean remember = rememberMe != null && rememberMe.equalsIgnoreCase("on");
|
||||||
|
@ -262,6 +249,7 @@ public class TokenService {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
case ACTIONS_REQUIRED:
|
case ACTIONS_REQUIRED:
|
||||||
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, formData.getFirst(AuthenticationManager.FORM_USERNAME));
|
||||||
return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
|
return oauth.processAccessCode(scopeParam, state, redirect, client, user, remember);
|
||||||
case ACCOUNT_DISABLED:
|
case ACCOUNT_DISABLED:
|
||||||
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
|
return Flows.forms(realm, request, uriInfo).setError(Messages.ACCOUNT_DISABLED).setFormData(formData).createLogin();
|
||||||
|
@ -317,6 +305,7 @@ public class TokenService {
|
||||||
requiredCredentialTypes.add(m.getType());
|
requiredCredentialTypes.add(m.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Validate here, so user is not created if password doesn't validate to passwordPolicy of current realm
|
||||||
String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
|
String error = Validation.validateRegistrationForm(formData, requiredCredentialTypes);
|
||||||
if (error == null) {
|
if (error == null) {
|
||||||
error = Validation.validatePassword(formData, realm.getPasswordPolicy());
|
error = Validation.validatePassword(formData, realm.getPasswordPolicy());
|
||||||
|
@ -344,7 +333,13 @@ public class TokenService {
|
||||||
UserCredentialModel credentials = new UserCredentialModel();
|
UserCredentialModel credentials = new UserCredentialModel();
|
||||||
credentials.setType(CredentialRepresentation.PASSWORD);
|
credentials.setType(CredentialRepresentation.PASSWORD);
|
||||||
credentials.setValue(formData.getFirst("password"));
|
credentials.setValue(formData.getFirst("password"));
|
||||||
realm.updateCredential(user, credentials);
|
try {
|
||||||
|
AuthenticationProviderManager.getManager(realm).updatePassword(username, formData.getFirst("password"));
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
// User already registered, but force him to update password
|
||||||
|
user.addRequiredAction(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
return Flows.forms(realm, request, uriInfo).setError(ape.getMessage()).createResponse(UserModel.RequiredAction.UPDATE_PASSWORD);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return processLogin(clientId, scopeParam, state, redirect, formData);
|
return processLogin(clientId, scopeParam, state, redirect, formData);
|
||||||
|
|
74
spi/authentication-model/pom.xml
Normal file
74
spi/authentication-model/pom.xml
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-spi</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-beta-1-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-authentication-model</artifactId>
|
||||||
|
<name>Keycloak Authentication Model Based</name>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-spi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,73 @@
|
||||||
|
package org.keycloak.spi.authentication.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
|
import org.keycloak.spi.authentication.AuthResult;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticatedUser;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProvider;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication provider, which delegates calling of all methods to specified realm
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public abstract class AbstractModelAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(AbstractModelAuthenticationProvider.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult validatePassword(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
|
||||||
|
RealmModel realm = getRealm(currentRealm, config);
|
||||||
|
|
||||||
|
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(realm, username);
|
||||||
|
|
||||||
|
// Ignore if user doesn't exists, so that other providers have opportunity to authenticate (and possibly create) him
|
||||||
|
if (user == null) {
|
||||||
|
return new AuthResult(AuthProviderStatus.IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean result = realm.validatePassword(user, password);
|
||||||
|
if (!result) {
|
||||||
|
return new AuthResult(AuthProviderStatus.IGNORE);
|
||||||
|
}
|
||||||
|
|
||||||
|
AuthenticatedUser authUser = createAuthenticatedUserInstance(user);
|
||||||
|
return new AuthResult(AuthProviderStatus.SUCCESS).setProviderName(getName()).setUser(authUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(RealmModel currentRealm, Map<String, String> config, String username, String password) throws AuthenticationProviderException {
|
||||||
|
RealmModel realm = getRealm(currentRealm, config);
|
||||||
|
|
||||||
|
// Validate password policy
|
||||||
|
String error = realm.getPasswordPolicy().validate(password);
|
||||||
|
if (error != null) {
|
||||||
|
throw new AuthenticationProviderException(error);
|
||||||
|
}
|
||||||
|
|
||||||
|
UserModel user = realm.getUser(username);
|
||||||
|
if (user == null) {
|
||||||
|
logger.debugf("User '%s' doesn't exists. Skip password update", username);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
UserCredentialModel cred = new UserCredentialModel();
|
||||||
|
cred.setType(CredentialRepresentation.PASSWORD);
|
||||||
|
cred.setValue(password);
|
||||||
|
|
||||||
|
realm.updateCredential(user, cred);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) throws AuthenticationProviderException;
|
||||||
|
|
||||||
|
protected abstract AuthenticatedUser createAuthenticatedUserInstance(UserModel user);
|
||||||
|
}
|
|
@ -0,0 +1,50 @@
|
||||||
|
package org.keycloak.spi.authentication.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.keycloak.models.KeycloakSession;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticatedUser;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractModelAuthenticationProvider, which delegates authentication operations to different (external) realm
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ExternalModelAuthenticationProvider extends AbstractModelAuthenticationProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RealmModel getRealm(RealmModel currentRealm, Map<String, String> configuration) throws AuthenticationProviderException {
|
||||||
|
String realmId = configuration.get(AuthProviderConstants.EXTERNAL_REALM_ID);
|
||||||
|
if (realmId == null) {
|
||||||
|
throw new AuthenticationProviderException("Option '" + AuthProviderConstants.EXTERNAL_REALM_ID + "' not specified in configuration");
|
||||||
|
}
|
||||||
|
|
||||||
|
KeycloakSession session = ResteasyProviderFactory.getContextData(KeycloakSession.class);
|
||||||
|
if (session == null) {
|
||||||
|
throw new AuthenticationProviderException("KeycloakSession not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
RealmModel realm = session.getRealm(realmId);
|
||||||
|
if (realm == null) {
|
||||||
|
throw new AuthenticationProviderException("Realm with id '" + realmId + "' doesn't exists");
|
||||||
|
}
|
||||||
|
return realm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
|
||||||
|
return new AuthenticatedUser(user.getId(), user.getLoginName())
|
||||||
|
.setName(user.getFirstName(), user.getLastName())
|
||||||
|
.setEmail(user.getEmail());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.keycloak.spi.authentication.model;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticatedUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AbstractModelAuthenticationProvider, which uses current realm to call operations on
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class ModelAuthenticationProvider extends AbstractModelAuthenticationProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return AuthProviderConstants.PROVIDER_NAME_MODEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected RealmModel getRealm(RealmModel currentRealm, Map<String, String> config) {
|
||||||
|
return currentRealm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AuthenticatedUser createAuthenticatedUserInstance(UserModel user) {
|
||||||
|
// We don't want AuthenticatedUser instance. Auto-registration won't never happen with this provider
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
org.keycloak.spi.authentication.model.ModelAuthenticationProvider
|
||||||
|
org.keycloak.spi.authentication.model.ExternalModelAuthenticationProvider
|
95
spi/authentication-picketlink/pom.xml
Normal file
95
spi/authentication-picketlink/pom.xml
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-spi</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-beta-1-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-authentication-picketlink</artifactId>
|
||||||
|
<name>Keycloak Authentication Picketlink Based</name>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-spi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.resteasy</groupId>
|
||||||
|
<artifactId>resteasy-jaxrs</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>log4j</groupId>
|
||||||
|
<artifactId>log4j</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-simple</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-common</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-impl</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.picketlink</groupId>
|
||||||
|
<artifactId>picketlink-idm-simple-schema</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,100 @@
|
||||||
|
package org.keycloak.spi.authentication.picketlink;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderStatus;
|
||||||
|
import org.keycloak.spi.authentication.AuthResult;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticatedUser;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProvider;
|
||||||
|
import org.keycloak.spi.authentication.AuthenticationProviderException;
|
||||||
|
import org.keycloak.spi.picketlink.PartitionManagerProvider;
|
||||||
|
import org.keycloak.util.ProviderLoader;
|
||||||
|
import org.picketlink.idm.IdentityManager;
|
||||||
|
import org.picketlink.idm.PartitionManager;
|
||||||
|
import org.picketlink.idm.credential.Credentials;
|
||||||
|
import org.picketlink.idm.credential.Password;
|
||||||
|
import org.picketlink.idm.credential.UsernamePasswordCredentials;
|
||||||
|
import org.picketlink.idm.model.basic.BasicModel;
|
||||||
|
import org.picketlink.idm.model.basic.User;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AuthenticationProvider, which delegates authentication to picketlink
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class PicketlinkAuthenticationProvider implements AuthenticationProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PicketlinkAuthenticationProvider.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return AuthProviderConstants.PROVIDER_NAME_PICKETLINK;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
|
||||||
|
IdentityManager identityManager = getIdentityManager(realm);
|
||||||
|
|
||||||
|
UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
|
||||||
|
credential.setUsername(username);
|
||||||
|
credential.setPassword(new Password(password.toCharArray()));
|
||||||
|
identityManager.validateCredentials(credential);
|
||||||
|
|
||||||
|
AuthResult result;
|
||||||
|
if (credential.getStatus() == Credentials.Status.VALID) {
|
||||||
|
result = new AuthResult(AuthProviderStatus.SUCCESS);
|
||||||
|
|
||||||
|
User picketlinkUser = BasicModel.getUser(identityManager, username);
|
||||||
|
AuthenticatedUser authenticatedUser = new AuthenticatedUser(picketlinkUser.getId(), picketlinkUser.getLoginName())
|
||||||
|
.setName(picketlinkUser.getFirstName(), picketlinkUser.getLastName())
|
||||||
|
.setEmail(picketlinkUser.getEmail());
|
||||||
|
result.setUser(authenticatedUser).setProviderName(getName());
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
logger.debugf("Username: %s, Credential status: %s", username, credential.getStatus());
|
||||||
|
return new AuthResult(AuthProviderStatus.IGNORE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException {
|
||||||
|
IdentityManager identityManager = getIdentityManager(realm);
|
||||||
|
|
||||||
|
User picketlinkUser = BasicModel.getUser(identityManager, username);
|
||||||
|
if (picketlinkUser == null) {
|
||||||
|
logger.debugf("User '%s' doesn't exists. Skip password update", username);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
identityManager.updateCredential(picketlinkUser, new Password(password.toCharArray()));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IdentityManager getIdentityManager(RealmModel realm) throws AuthenticationProviderException {
|
||||||
|
IdentityManager identityManager = ResteasyProviderFactory.getContextData(IdentityManager.class);
|
||||||
|
if (identityManager == null) {
|
||||||
|
Iterable<PartitionManagerProvider> providers = ProviderLoader.load(PartitionManagerProvider.class);
|
||||||
|
|
||||||
|
// TODO: Priority?
|
||||||
|
PartitionManager partitionManager = null;
|
||||||
|
for (PartitionManagerProvider provider : providers) {
|
||||||
|
partitionManager = provider.getPartitionManager(realm);
|
||||||
|
if (partitionManager != null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partitionManager == null) {
|
||||||
|
throw new AuthenticationProviderException("Not able to locate PartitionManager with any PartitionManagerProvider");
|
||||||
|
}
|
||||||
|
|
||||||
|
identityManager = partitionManager.createIdentityManager();
|
||||||
|
ResteasyProviderFactory.pushContext(IdentityManager.class, identityManager);
|
||||||
|
}
|
||||||
|
return identityManager;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.keycloak.spi.picketlink;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.picketlink.idm.PartitionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface PartitionManagerProvider {
|
||||||
|
|
||||||
|
PartitionManager getPartitionManager(RealmModel realm);
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.keycloak.spi.picketlink.impl;
|
||||||
|
|
||||||
|
import org.picketlink.idm.IdentityManager;
|
||||||
|
import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
|
||||||
|
import org.picketlink.idm.model.Account;
|
||||||
|
import org.picketlink.idm.model.basic.User;
|
||||||
|
import org.picketlink.idm.spi.IdentityContext;
|
||||||
|
|
||||||
|
import static org.picketlink.idm.IDMLog.CREDENTIAL_LOGGER;
|
||||||
|
import static org.picketlink.idm.model.basic.BasicModel.getUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class LDAPAgentIgnoreCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Account getAccount(IdentityContext context, String loginName) {
|
||||||
|
IdentityManager identityManager = getIdentityManager(context);
|
||||||
|
|
||||||
|
if (CREDENTIAL_LOGGER.isDebugEnabled()) {
|
||||||
|
CREDENTIAL_LOGGER.debugf("Trying to find account [%s] using default account type [%s]", loginName, User.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getUser(identityManager, loginName);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
package org.keycloak.spi.picketlink.impl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class LdapConstants {
|
||||||
|
|
||||||
|
public static final String CONNECTION_URL = "connectionUrl";
|
||||||
|
public static final String BASE_DN = "baseDn";
|
||||||
|
public static final String USER_DN_SUFFIX = "userDnSuffix";
|
||||||
|
public static final String BIND_DN = "bindDn";
|
||||||
|
public static final String BIND_CREDENTIAL = "bindCredential";
|
||||||
|
}
|
|
@ -0,0 +1,88 @@
|
||||||
|
package org.keycloak.spi.picketlink.impl;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.picketlink.idm.PartitionManager;
|
||||||
|
import org.picketlink.idm.config.IdentityConfigurationBuilder;
|
||||||
|
import org.picketlink.idm.internal.DefaultPartitionManager;
|
||||||
|
import org.picketlink.idm.model.basic.Agent;
|
||||||
|
import org.picketlink.idm.model.basic.User;
|
||||||
|
|
||||||
|
import static org.picketlink.common.constants.LDAPConstants.CN;
|
||||||
|
import static org.picketlink.common.constants.LDAPConstants.CREATE_TIMESTAMP;
|
||||||
|
import static org.picketlink.common.constants.LDAPConstants.EMAIL;
|
||||||
|
import static org.picketlink.common.constants.LDAPConstants.SN;
|
||||||
|
import static org.picketlink.common.constants.LDAPConstants.UID;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class PartitionManagerRegistry {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(PartitionManagerRegistry.class);
|
||||||
|
|
||||||
|
private Map<String, PartitionManagerContext> partitionManagers = new ConcurrentHashMap<String, PartitionManagerContext>();
|
||||||
|
|
||||||
|
public PartitionManager getPartitionManager(RealmModel realm) {
|
||||||
|
Map<String,String> ldapConfig = realm.getLdapServerConfig();
|
||||||
|
if (ldapConfig == null || ldapConfig.isEmpty()) {
|
||||||
|
logger.warnf("Ldap configuration is missing for realm '%s'", realm.getName());
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionManagerContext context = partitionManagers.get(realm.getId());
|
||||||
|
|
||||||
|
// Ldap config might have changed for the realm. In this case, we must re-initialize
|
||||||
|
if (context == null || !ldapConfig.equals(context.config)) {
|
||||||
|
logger.infof("Creating new partition manager for the realm: %s", realm.getId());
|
||||||
|
PartitionManager manager = createPartitionManager(ldapConfig);
|
||||||
|
context = new PartitionManagerContext(ldapConfig, manager);
|
||||||
|
partitionManagers.put(realm.getId(), context);
|
||||||
|
}
|
||||||
|
return context.partitionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param ldapConfig from realm
|
||||||
|
* @return PartitionManager instance based on LDAP store
|
||||||
|
*/
|
||||||
|
protected PartitionManager createPartitionManager(Map<String,String> ldapConfig) {
|
||||||
|
IdentityConfigurationBuilder builder = new IdentityConfigurationBuilder();
|
||||||
|
|
||||||
|
// Use same mapping for User and Agent for now
|
||||||
|
builder
|
||||||
|
.named("SIMPLE_LDAP_STORE_CONFIG")
|
||||||
|
.stores()
|
||||||
|
.ldap()
|
||||||
|
.addCredentialHandler(LDAPAgentIgnoreCredentialHandler.class)
|
||||||
|
.baseDN(ldapConfig.get(LdapConstants.BASE_DN))
|
||||||
|
.bindDN(ldapConfig.get(LdapConstants.BIND_DN))
|
||||||
|
.bindCredential(ldapConfig.get(LdapConstants.BIND_CREDENTIAL))
|
||||||
|
.url(ldapConfig.get(LdapConstants.CONNECTION_URL))
|
||||||
|
.supportAllFeatures()
|
||||||
|
.mapping(User.class)
|
||||||
|
.baseDN(ldapConfig.get(LdapConstants.USER_DN_SUFFIX))
|
||||||
|
.objectClasses("inetOrgPerson", "organizationalPerson")
|
||||||
|
.attribute("loginName", UID, true)
|
||||||
|
.attribute("firstName", CN)
|
||||||
|
.attribute("lastName", SN)
|
||||||
|
.attribute("email", EMAIL)
|
||||||
|
.readOnlyAttribute("createdDate", CREATE_TIMESTAMP);
|
||||||
|
|
||||||
|
return new DefaultPartitionManager(builder.buildAll());
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PartitionManagerContext {
|
||||||
|
|
||||||
|
private PartitionManagerContext(Map<String,String> config, PartitionManager manager) {
|
||||||
|
this.config = config;
|
||||||
|
this.partitionManager = manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String,String> config;
|
||||||
|
private PartitionManager partitionManager;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.keycloak.spi.picketlink.impl;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.jboss.resteasy.spi.ResteasyProviderFactory;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.spi.picketlink.PartitionManagerProvider;
|
||||||
|
import org.keycloak.spi.picketlink.impl.PartitionManagerRegistry;
|
||||||
|
import org.keycloak.util.KeycloakRegistry;
|
||||||
|
import org.picketlink.idm.PartitionManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Obtains {@link PartitionManager} instances from shared {@link PartitionManagerRegistry} and uses realm configuration for it
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class RealmPartitionManagerProvider implements PartitionManagerProvider {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(RealmPartitionManagerProvider.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PartitionManager getPartitionManager(RealmModel realm) {
|
||||||
|
KeycloakRegistry registry = ResteasyProviderFactory.getContextData(KeycloakRegistry.class) ;
|
||||||
|
if (registry == null) {
|
||||||
|
logger.warn("KeycloakRegistry not found");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
PartitionManagerRegistry partitionManagerRegistry = registry.getService(PartitionManagerRegistry.class);
|
||||||
|
if (partitionManagerRegistry == null) {
|
||||||
|
partitionManagerRegistry = new PartitionManagerRegistry();
|
||||||
|
partitionManagerRegistry = registry.putServiceIfAbsent(PartitionManagerRegistry.class, partitionManagerRegistry);
|
||||||
|
logger.info("Pushed PartitionManagerRegistry component");
|
||||||
|
}
|
||||||
|
|
||||||
|
return partitionManagerRegistry.getPartitionManager(realm);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.spi.authentication.picketlink.PicketlinkAuthenticationProvider
|
|
@ -0,0 +1 @@
|
||||||
|
org.keycloak.spi.picketlink.impl.RealmPartitionManagerProvider
|
48
spi/authentication-spi/pom.xml
Normal file
48
spi/authentication-spi/pom.xml
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-spi</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-beta-1-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>keycloak-authentication-spi</artifactId>
|
||||||
|
<name>Keycloak Authentication SPI</name>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-core</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-api</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
<artifactId>jboss-logging</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<source>1.6</source>
|
||||||
|
<target>1.6</target>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
</project>
|
|
@ -0,0 +1,16 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthProviderConstants {
|
||||||
|
|
||||||
|
public static final String PROVIDER_NAME_MODEL = "model";
|
||||||
|
public static final String PROVIDER_NAME_EXTERNAL_MODEL = "externalModel";
|
||||||
|
public static final String PROVIDER_NAME_PICKETLINK = "picketlink";
|
||||||
|
|
||||||
|
public static final String DEFAULT_PROVIDER = PROVIDER_NAME_MODEL;
|
||||||
|
|
||||||
|
// Used in external-model provider
|
||||||
|
public static final String EXTERNAL_REALM_ID = "externalRealmId";
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Result of authentication by AuthenticationProvider
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public enum AuthProviderStatus {
|
||||||
|
|
||||||
|
// Ignore means that AuthenticationProvider wasn't able to authenticate result, but it should postpone authentication to next provider (for example user didn't exists in realm)
|
||||||
|
SUCCESS, FAILED, IGNORE
|
||||||
|
}
|
|
@ -0,0 +1,44 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthResult {
|
||||||
|
|
||||||
|
// Status of authentication
|
||||||
|
private final AuthProviderStatus authProviderStatus;
|
||||||
|
|
||||||
|
// Provider, which authenticated user
|
||||||
|
private String providerName;
|
||||||
|
|
||||||
|
// filled usually only in case of successful authentication and just with some Authentication providers
|
||||||
|
private AuthenticatedUser authenticatedUser;
|
||||||
|
|
||||||
|
public AuthResult(AuthProviderStatus authProviderStatus) {
|
||||||
|
this.authProviderStatus = authProviderStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthResult setProviderName(String providerName) {
|
||||||
|
this.providerName = providerName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthResult setUser(AuthenticatedUser user) {
|
||||||
|
this.authenticatedUser = user;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthProviderStatus getAuthProviderStatus() {
|
||||||
|
return authProviderStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getProviderName() {
|
||||||
|
return providerName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticatedUser getAuthenticatedUser() {
|
||||||
|
return authenticatedUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticatedUser {
|
||||||
|
|
||||||
|
private String id;
|
||||||
|
private String username;
|
||||||
|
private String firstName;
|
||||||
|
private String lastName;
|
||||||
|
private String email;
|
||||||
|
|
||||||
|
public AuthenticatedUser(String id, String username) {
|
||||||
|
this.id = id;
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticatedUser setId(String id) {
|
||||||
|
this.id = id;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticatedUser setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getFirstName() {
|
||||||
|
return firstName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticatedUser setName(String name) {
|
||||||
|
int i = name.lastIndexOf(' ');
|
||||||
|
if (i != -1) {
|
||||||
|
firstName = name.substring(0, i);
|
||||||
|
lastName = name.substring(i + 1);
|
||||||
|
} else {
|
||||||
|
firstName = name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticatedUser setName(String firstName, String lastName) {
|
||||||
|
this.firstName = firstName;
|
||||||
|
this.lastName = lastName;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLastName() {
|
||||||
|
return lastName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticatedUser setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,37 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public interface AuthenticationProvider {
|
||||||
|
|
||||||
|
String getName();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Standard Authentication flow
|
||||||
|
*
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
AuthResult validatePassword(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update credential
|
||||||
|
*
|
||||||
|
* @param realm
|
||||||
|
* @param configuration
|
||||||
|
* @param username
|
||||||
|
* @param password
|
||||||
|
* @return true if credential has been successfully updated
|
||||||
|
* @throws AuthenticationProviderException
|
||||||
|
*/
|
||||||
|
boolean updateCredential(RealmModel realm, Map<String, String> configuration, String username, String password) throws AuthenticationProviderException;
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationProviderException extends Exception {
|
||||||
|
|
||||||
|
private static final long serialVersionUID = 15L;
|
||||||
|
|
||||||
|
protected AuthenticationProviderException() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProviderException(String message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProviderException(Throwable cause) {
|
||||||
|
super(cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProviderException(String message, Throwable cause) {
|
||||||
|
super(message, cause);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,125 @@
|
||||||
|
package org.keycloak.spi.authentication;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.jboss.logging.Logger;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.util.ProviderLoader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access point to authentication SPI. It finds configured and available {@link AuthenticationProvider} instances for current realm
|
||||||
|
* and then delegates method call to them.
|
||||||
|
*
|
||||||
|
* Example of usage: AuthenticationProviderManager.getManager(realm).validateUser("joe", "password");
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class AuthenticationProviderManager {
|
||||||
|
|
||||||
|
private static final Logger logger = Logger.getLogger(AuthenticationProviderManager.class);
|
||||||
|
private static final AuthenticationProviderModel DEFAULT_PROVIDER = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, true, Collections.EMPTY_MAP);
|
||||||
|
|
||||||
|
private final RealmModel realm;
|
||||||
|
private final Map<String, AuthenticationProvider> delegates;
|
||||||
|
|
||||||
|
public static AuthenticationProviderManager getManager(RealmModel realm) {
|
||||||
|
Iterable<AuthenticationProvider> providers = load();
|
||||||
|
|
||||||
|
Map<String, AuthenticationProvider> providersMap = new HashMap<String, AuthenticationProvider>();
|
||||||
|
for (AuthenticationProvider provider : providers) {
|
||||||
|
providersMap.put(provider.getName(), provider);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AuthenticationProviderManager(realm, providersMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Iterable<AuthenticationProvider> load() {
|
||||||
|
return ProviderLoader.load(AuthenticationProvider.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthenticationProviderManager(RealmModel realm, Map<String, AuthenticationProvider> delegates) {
|
||||||
|
this.realm = realm;
|
||||||
|
this.delegates = delegates;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AuthResult validatePassword(String username, String password) {
|
||||||
|
List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
|
||||||
|
|
||||||
|
for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
|
||||||
|
String providerName = authProviderConfig.getProviderName();
|
||||||
|
|
||||||
|
AuthenticationProvider delegate = getDelegate(providerName);
|
||||||
|
if (delegate == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
AuthResult currentResult = delegate.validatePassword(realm, authProviderConfig.getConfig(), username, password);
|
||||||
|
logger.debugf("Authentication provider '%s' finished with '%s' for authentication of '%s'", delegate.getName(), currentResult.toString(), username);
|
||||||
|
|
||||||
|
if (currentResult.getAuthProviderStatus() == AuthProviderStatus.SUCCESS || currentResult.getAuthProviderStatus() == AuthProviderStatus.FAILED) {
|
||||||
|
return currentResult;
|
||||||
|
}
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
logger.warn(ape.getMessage(), ape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.debugf("Not able to authenticate '%s' with any authentication provider", username);
|
||||||
|
|
||||||
|
return new AuthResult(AuthProviderStatus.FAILED);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updatePassword(String username, String password) throws AuthenticationProviderException {
|
||||||
|
List<AuthenticationProviderModel> configuredProviders = getConfiguredProviders(realm);
|
||||||
|
|
||||||
|
for (AuthenticationProviderModel authProviderConfig : configuredProviders) {
|
||||||
|
|
||||||
|
// Update just those, which support password update
|
||||||
|
if (authProviderConfig.isPasswordUpdateSupported()) {
|
||||||
|
String providerName = authProviderConfig.getProviderName();
|
||||||
|
AuthenticationProvider delegate = getDelegate(providerName);
|
||||||
|
if (delegate == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
delegate.updateCredential(realm, authProviderConfig.getConfig(), username, password);
|
||||||
|
logger.debugf("Updated password in authentication provider '%s' for user '%s'", delegate.getName(), username);
|
||||||
|
} catch (AuthenticationProviderException ape) {
|
||||||
|
// Rethrow it to upper layer
|
||||||
|
logger.warn("Failed to update password", ape);
|
||||||
|
throw ape;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
logger.debugf("Skip password update for authentication provider '%s' for user '%s'", authProviderConfig.getProviderName(), username);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AuthenticationProvider getDelegate(String providerName) {
|
||||||
|
AuthenticationProvider delegate = delegates.get(providerName);
|
||||||
|
if (delegate == null) {
|
||||||
|
logger.warnf("Configured provider with name '%s' not found", providerName);
|
||||||
|
}
|
||||||
|
return delegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AuthenticationProviderModel> getConfiguredProviders(RealmModel realm) {
|
||||||
|
List<AuthenticationProviderModel> configuredProviders = realm.getAuthenticationProviders();
|
||||||
|
|
||||||
|
// Use model based authentication of current realm by default
|
||||||
|
if (configuredProviders == null || configuredProviders.isEmpty()) {
|
||||||
|
configuredProviders = new ArrayList<AuthenticationProviderModel>();
|
||||||
|
configuredProviders.add(DEFAULT_PROVIDER);
|
||||||
|
}
|
||||||
|
|
||||||
|
return configuredProviders;
|
||||||
|
}
|
||||||
|
}
|
24
spi/pom.xml
Normal file
24
spi/pom.xml
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>keycloak-parent</artifactId>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<version>1.0-beta-1-SNAPSHOT</version>
|
||||||
|
<relativePath>../pom.xml</relativePath>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<packaging>pom</packaging>
|
||||||
|
|
||||||
|
<artifactId>keycloak-spi</artifactId>
|
||||||
|
<name>Keycloak SPI</name>
|
||||||
|
<description />
|
||||||
|
|
||||||
|
<modules>
|
||||||
|
<module>authentication-spi</module>
|
||||||
|
<module>authentication-model</module>
|
||||||
|
<module>authentication-picketlink</module>
|
||||||
|
</modules>
|
||||||
|
|
||||||
|
</project>
|
|
@ -139,6 +139,16 @@
|
||||||
<artifactId>keycloak-login-freemarker</artifactId>
|
<artifactId>keycloak-login-freemarker</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-spi</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-authentication-model</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.logging</groupId>
|
<groupId>org.jboss.logging</groupId>
|
||||||
|
@ -255,6 +265,12 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.icegreen</groupId>
|
<groupId>com.icegreen</groupId>
|
||||||
<artifactId>greenmail</artifactId>
|
<artifactId>greenmail</artifactId>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.seleniumhq.selenium</groupId>
|
<groupId>org.seleniumhq.selenium</groupId>
|
||||||
|
@ -284,6 +300,13 @@
|
||||||
<artifactId>picketlink-common</artifactId>
|
<artifactId>picketlink-common</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- This adds couple of other dependencies (like picketlink) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-model-tests</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
|
|
|
@ -10,9 +10,11 @@
|
||||||
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
package org.keycloak.testsuite.forms;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.FixMethodOrder;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.RuleChain;
|
||||||
|
import org.junit.rules.TestRule;
|
||||||
|
import org.junit.runners.MethodSorters;
|
||||||
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.models.AuthenticationProviderModel;
|
||||||
|
import org.keycloak.models.PasswordPolicy;
|
||||||
|
import org.keycloak.models.RealmModel;
|
||||||
|
import org.keycloak.models.UserCredentialModel;
|
||||||
|
import org.keycloak.models.UserModel;
|
||||||
|
import org.keycloak.representations.idm.CredentialRepresentation;
|
||||||
|
import org.keycloak.services.managers.RealmManager;
|
||||||
|
import org.keycloak.spi.authentication.AuthProviderConstants;
|
||||||
|
import org.keycloak.testsuite.OAuthClient;
|
||||||
|
import org.keycloak.testsuite.pages.AccountPasswordPage;
|
||||||
|
import org.keycloak.testsuite.pages.AccountUpdateProfilePage;
|
||||||
|
import org.keycloak.testsuite.pages.AppPage;
|
||||||
|
import org.keycloak.testsuite.pages.LoginPage;
|
||||||
|
import org.keycloak.testsuite.rule.KeycloakRule;
|
||||||
|
import org.keycloak.testsuite.rule.LDAPRule;
|
||||||
|
import org.keycloak.testsuite.rule.WebResource;
|
||||||
|
import org.keycloak.testsuite.rule.WebRule;
|
||||||
|
import org.openqa.selenium.WebDriver;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
|
||||||
|
public class AuthProvidersIntegrationTest {
|
||||||
|
|
||||||
|
private static LDAPRule ldapRule = new LDAPRule();
|
||||||
|
|
||||||
|
private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
addUser(appRealm, "mary", "mary@test.com", "password-app");
|
||||||
|
addUser(adminstrationRealm, "mary", "mary@admin.com", "password-admin");
|
||||||
|
|
||||||
|
AuthenticationProviderModel modelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_MODEL, false, Collections.EMPTY_MAP);
|
||||||
|
AuthenticationProviderModel picketlinkProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_PICKETLINK, true, Collections.EMPTY_MAP);
|
||||||
|
|
||||||
|
// Configure LDAP
|
||||||
|
ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
|
||||||
|
|
||||||
|
// Delegate authentication to admin realm
|
||||||
|
Map<String,String> config = new HashMap<String,String>();
|
||||||
|
config.put(AuthProviderConstants.EXTERNAL_REALM_ID, adminstrationRealm.getId());
|
||||||
|
AuthenticationProviderModel externalModelProvider = new AuthenticationProviderModel(AuthProviderConstants.PROVIDER_NAME_EXTERNAL_MODEL, true, config);
|
||||||
|
|
||||||
|
appRealm.setAuthenticationProviders(Arrays.asList(modelProvider, picketlinkProvider, externalModelProvider));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
@ClassRule
|
||||||
|
public static TestRule chain = RuleChain
|
||||||
|
.outerRule(ldapRule)
|
||||||
|
.around(keycloakRule);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public WebRule webRule = new WebRule(this);
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected OAuthClient oauth;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected WebDriver driver;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AppPage appPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected LoginPage loginPage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AccountUpdateProfilePage profilePage;
|
||||||
|
|
||||||
|
@WebResource
|
||||||
|
protected AccountPasswordPage changePasswordPage;
|
||||||
|
|
||||||
|
private static UserModel addUser(RealmModel realm, String username, String email, String password) {
|
||||||
|
UserModel user = realm.addUser(username);
|
||||||
|
user.setEmail(email);
|
||||||
|
user.setEnabled(true);
|
||||||
|
|
||||||
|
UserCredentialModel creds = new UserCredentialModel();
|
||||||
|
creds.setType(CredentialRepresentation.PASSWORD);
|
||||||
|
creds.setValue(password);
|
||||||
|
|
||||||
|
realm.updateCredential(user, creds);
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginClassic() {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("mary", "password-app");
|
||||||
|
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginExternalModel() {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("mary", "password-admin");
|
||||||
|
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loginLdap() {
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("john", "password");
|
||||||
|
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void passwordChangeExternalModel() {
|
||||||
|
// Set password-policy for admin realm
|
||||||
|
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
adminstrationRealm.setPasswordPolicy(new PasswordPolicy("length(6)"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
changePasswordPage.open();
|
||||||
|
loginPage.login("mary", "password-admin");
|
||||||
|
|
||||||
|
// Can't update to "pass" due to passwordPolicy
|
||||||
|
changePasswordPage.changePassword("password-admin", "pass", "pass");
|
||||||
|
Assert.assertEquals("Invalid password: minimum length 6", profilePage.getError());
|
||||||
|
|
||||||
|
changePasswordPage.changePassword("password-app", "password-updated", "password-updated");
|
||||||
|
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
|
||||||
|
changePasswordPage.logout();
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("mary", "password-updated");
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
keycloakRule.configure(new KeycloakRule.KeycloakSetup() {
|
||||||
|
@Override
|
||||||
|
public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) {
|
||||||
|
adminstrationRealm.setPasswordPolicy(new PasswordPolicy(null));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void passwordChangeLdap() {
|
||||||
|
changePasswordPage.open();
|
||||||
|
loginPage.login("john", "password");
|
||||||
|
changePasswordPage.changePassword("password", "new-password", "new-password");
|
||||||
|
|
||||||
|
Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
|
||||||
|
|
||||||
|
changePasswordPage.logout();
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("john", "password");
|
||||||
|
Assert.assertEquals("Invalid username or password.", loginPage.getError());
|
||||||
|
|
||||||
|
loginPage.open();
|
||||||
|
loginPage.login("john", "new-password");
|
||||||
|
Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
package org.keycloak.testsuite.rule;
|
||||||
|
|
||||||
|
import org.junit.rules.ExternalResource;
|
||||||
|
import org.keycloak.model.test.LDAPEmbeddedServer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||||
|
*/
|
||||||
|
public class LDAPRule extends ExternalResource {
|
||||||
|
|
||||||
|
private LDAPEmbeddedServer embeddedServer;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void before() throws Throwable {
|
||||||
|
try {
|
||||||
|
embeddedServer = new LDAPEmbeddedServer();
|
||||||
|
embeddedServer.setup();
|
||||||
|
embeddedServer.importLDIF("ldap/users.ldif");
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error starting Embedded LDAP server.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void after() {
|
||||||
|
try {
|
||||||
|
embeddedServer.tearDown();
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("Error starting Embedded LDAP server.", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LDAPEmbeddedServer getEmbeddedServer() {
|
||||||
|
return embeddedServer;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
idm.test.ldap.connection.url=ldap\://localhost\:10389
|
||||||
|
idm.test.ldap.base.dn=dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.roles.dn.suffix=ou\=Roles,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.group.dn.suffix=ou\=Groups,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.user.dn.suffix=ou\=People,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.agent.dn.suffix=ou\=Agent,dc\=keycloak,dc\=org
|
||||||
|
idm.test.ldap.start.embedded.ldap.server=true
|
||||||
|
idm.test.ldap.bind.dn=uid\=admin,ou\=system
|
||||||
|
idm.test.ldap.bind.credential=secret
|
31
testsuite/integration/src/test/resources/ldap/users.ldif
Normal file
31
testsuite/integration/src/test/resources/ldap/users.ldif
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
dn: dc=keycloak,dc=org
|
||||||
|
objectclass: dcObject
|
||||||
|
objectclass: organization
|
||||||
|
o: Keycloak
|
||||||
|
dc: Keycloak
|
||||||
|
|
||||||
|
dn: ou=People,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: organizationalUnit
|
||||||
|
ou: People
|
||||||
|
|
||||||
|
dn: ou=Roles,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: organizationalUnit
|
||||||
|
ou: Roles
|
||||||
|
|
||||||
|
dn: ou=Groups,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: organizationalUnit
|
||||||
|
ou: Groups
|
||||||
|
|
||||||
|
dn: uid=john,ou=People,dc=keycloak,dc=org
|
||||||
|
objectclass: top
|
||||||
|
objectclass: uidObject
|
||||||
|
objectclass: person
|
||||||
|
objectclass: inetOrgPerson
|
||||||
|
uid: john
|
||||||
|
cn: John
|
||||||
|
sn: Doe
|
||||||
|
mail: john@email.org
|
||||||
|
userPassword: password
|
|
@ -10,12 +10,14 @@
|
||||||
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
<class>org.keycloak.models.jpa.entities.OAuthClientEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.ApplicationRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
<class>org.keycloak.models.jpa.entities.RealmRoleEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
|
||||||
|
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.UserRoleMappingEntity</class>
|
||||||
<class>org.keycloak.models.jpa.entities.UserScopeMappingEntity</class>
|
<class>org.keycloak.models.jpa.entities.ScopeMappingEntity</class>
|
||||||
|
|
||||||
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
<exclude-unlisted-classes>true</exclude-unlisted-classes>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue