keycloak-aplcache
Changes
export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java 1(+1 -0)
federation/ldap/pom.xml 88(+88 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java 89(+89 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java 47(+47 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java 168(+168 -0)
federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java 160(+160 -0)
federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory 1(+1 -0)
federation/pom.xml 22(+22 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java 4(+2 -2)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java 6(+6 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java 7(+7 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java 4(+2 -2)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java 13(+13 -0)
model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java 13(+13 -0)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java 2(+1 -1)
model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java 2(+1 -1)
pom.xml 1(+1 -0)
testsuite/integration/pom.xml 5(+5 -0)
Details
diff --git a/authentication/authentication-picketlink/pom.xml b/authentication/authentication-picketlink/pom.xml
index 3ca1a10..37ae937 100755
--- a/authentication/authentication-picketlink/pom.xml
+++ b/authentication/authentication-picketlink/pom.xml
@@ -4,7 +4,7 @@
<artifactId>keycloak-authentication-parent</artifactId>
<groupId>org.keycloak</groupId>
<version>1.0-beta-4-SNAPSHOT</version>
- <relativePath>../pom.xml</relativePath>
+ <relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml
index 276f6ba..7511ed2 100755
--- a/connections/jpa/src/main/resources/META-INF/persistence.xml
+++ b/connections/jpa/src/main/resources/META-INF/persistence.xml
@@ -9,6 +9,7 @@
<class>org.keycloak.models.jpa.entities.RealmEntity</class>
<class>org.keycloak.models.jpa.entities.RequiredCredentialEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticationProviderEntity</class>
+ <class>org.keycloak.models.jpa.entities.FederationProviderEntity</class>
<class>org.keycloak.models.jpa.entities.RoleEntity</class>
<class>org.keycloak.models.jpa.entities.SocialLinkEntity</class>
<class>org.keycloak.models.jpa.entities.AuthenticationLinkEntity</class>
diff --git a/core/src/main/java/org/keycloak/representations/FederationProviderRepresentation.java b/core/src/main/java/org/keycloak/representations/FederationProviderRepresentation.java
new file mode 100755
index 0000000..e14f96e
--- /dev/null
+++ b/core/src/main/java/org/keycloak/representations/FederationProviderRepresentation.java
@@ -0,0 +1,55 @@
+package org.keycloak.representations;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class FederationProviderRepresentation {
+
+ private String id;
+ private String providerName;
+ 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 Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FederationProviderRepresentation that = (FederationProviderRepresentation) o;
+
+ if (!id.equals(that.id)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return id.hashCode();
+ }
+}
diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
index 5a65329..584e639 100755
--- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java
@@ -1,5 +1,7 @@
package org.keycloak.representations.idm;
+import org.keycloak.representations.FederationProviderRepresentation;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -56,6 +58,7 @@ public class RealmRepresentation {
protected Map<String, String> smtpServer;
protected Map<String, String> ldapServer;
protected List<AuthenticationProviderRepresentation> authenticationProviders;
+ protected List<FederationProviderRepresentation> federationProviders;
protected String loginTheme;
protected String accountTheme;
protected String adminTheme;
@@ -467,4 +470,12 @@ public class RealmRepresentation {
public void setAuditListeners(List<String> auditListeners) {
this.auditListeners = auditListeners;
}
+
+ public List<FederationProviderRepresentation> getFederationProviders() {
+ return federationProviders;
+ }
+
+ public void setFederationProviders(List<FederationProviderRepresentation> federationProviders) {
+ this.federationProviders = federationProviders;
+ }
}
diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index abc7846..2578983 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -21,6 +21,7 @@ public class UserRepresentation {
protected String lastName;
protected String email;
protected AuthenticationLinkRepresentation authenticationLink;
+ protected String federationLink;
protected Map<String, String> attributes;
protected List<CredentialRepresentation> credentials;
protected List<String> requiredActions;
@@ -170,4 +171,12 @@ public class UserRepresentation {
public void setApplicationRoles(Map<String, List<String>> applicationRoles) {
this.applicationRoles = applicationRoles;
}
+
+ public String getFederationLink() {
+ return federationLink;
+ }
+
+ public void setFederationLink(String federationLink) {
+ this.federationLink = federationLink;
+ }
}
diff --git a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index dd3c5dd..4c1cc5b 100755
--- a/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/export-import/export-import-api/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -308,6 +308,7 @@ public class ExportUtils {
credReps.add(credRep);
}
userRep.setCredentials(credReps);
+ userRep.setFederationLink(user.getFederationLink());
return userRep;
}
federation/ldap/pom.xml 88(+88 -0)
diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml
new file mode 100755
index 0000000..5f8f887
--- /dev/null
+++ b/federation/ldap/pom.xml
@@ -0,0 +1,88 @@
+<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-authentication-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>keycloak-ldap-federation</artifactId>
+ <name>Keycloak LDAP Federation</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.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>${maven.compiler.source}</source>
+ <target>${maven.compiler.target}</target>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java
new file mode 100755
index 0000000..1ff6c11
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/KeycloakLDAPIdentityStore.java
@@ -0,0 +1,89 @@
+package org.keycloak.federation.ldap;
+
+import org.keycloak.models.utils.reflection.Reflections;
+import org.picketlink.idm.config.LDAPMappingConfiguration;
+import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
+import org.picketlink.idm.ldap.internal.LDAPOperationManager;
+import org.picketlink.idm.model.AttributedType;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.query.IdentityQuery;
+import org.picketlink.idm.spi.IdentityContext;
+
+import javax.naming.directory.BasicAttributes;
+import java.lang.reflect.Method;
+
+import static org.picketlink.common.constants.LDAPConstants.*;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+public class KeycloakLDAPIdentityStore extends LDAPIdentityStore {
+
+ public static Method GET_OPERATION_MANAGER_METHOD;
+ public static Method CREATE_SEARCH_FILTER_METHOD;
+ public static Method EXTRACT_ATTRIBUTES_METHOD;
+ public static Method GET_ENTRY_IDENTIFIER_METHOD;
+
+ public static final String SAM_ACCOUNT_NAME = "sAMAccountName";
+
+ static {
+ GET_OPERATION_MANAGER_METHOD = getMethodOnLDAPStore("getOperationManager");
+ CREATE_SEARCH_FILTER_METHOD = getMethodOnLDAPStore("createIdentityTypeSearchFilter", IdentityQuery.class, LDAPMappingConfiguration.class);
+ EXTRACT_ATTRIBUTES_METHOD = getMethodOnLDAPStore("extractAttributes", AttributedType.class, boolean.class);
+ GET_ENTRY_IDENTIFIER_METHOD = getMethodOnLDAPStore("getEntryIdentifier", AttributedType.class);
+ }
+
+ @Override
+ public void addAttributedType(IdentityContext context, AttributedType attributedType) {
+ LDAPMappingConfiguration userMappingConfig = getConfig().getMappingConfig(attributedType.getClass());
+ String ldapUsernameAttrName = userMappingConfig.getMappedProperties().get(userMappingConfig.getIdProperty().getName());
+
+ if (getConfig().isActiveDirectory() && SAM_ACCOUNT_NAME.equals(ldapUsernameAttrName)) {
+ // TODO: pain, but everything in picketlink is private... Improve if possible...
+ LDAPOperationManager operationManager = Reflections.invokeMethod(false, GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, this);
+
+ String cn = getCommonName(attributedType);
+ String bindingDN = CN + EQUAL + cn + COMMA + userMappingConfig.getBaseDN();
+
+ BasicAttributes ldapAttributes = Reflections.invokeMethod(false, EXTRACT_ATTRIBUTES_METHOD, BasicAttributes.class, this, attributedType, true);
+ ldapAttributes.put(CN, cn);
+
+ operationManager.createSubContext(bindingDN, ldapAttributes);
+
+ String ldapId = Reflections.invokeMethod(false, GET_ENTRY_IDENTIFIER_METHOD, String.class, this, attributedType);
+ attributedType.setId(ldapId);
+ } else {
+ super.addAttributedType(context, attributedType);
+ }
+ }
+
+ // Hack as methods are protected on LDAPIdentityStore :/
+ public static Method getMethodOnLDAPStore(String methodName, Class... classes) {
+ try {
+ Method m = LDAPIdentityStore.class.getDeclaredMethod(methodName, classes);
+ m.setAccessible(true);
+ return m;
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ protected String getCommonName(AttributedType aType) {
+ User user = (User)aType;
+ String fullName;
+ if (user.getFirstName() != null && user.getLastName() != null) {
+ fullName = user.getFirstName() + " " + user.getLastName();
+ } else if (user.getFirstName() != null && user.getFirstName().trim().length() > 0) {
+ fullName = user.getFirstName();
+ } else {
+ fullName = user.getLastName();
+ }
+
+ // Fallback to loginName
+ if (fullName == null || fullName.trim().length() == 0) {
+ fullName = user.getLoginName();
+ }
+
+ return fullName;
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
new file mode 100755
index 0000000..4215948
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java
@@ -0,0 +1,329 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.FederationProvider;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RealmModel;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.SocialLinkModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserModel;
+import org.picketlink.idm.IdentityManagementException;
+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.TOTPCredential;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
+import static org.picketlink.idm.IDMMessages.MESSAGES;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LDAPFederationProvider implements FederationProvider {
+ private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class);
+
+ protected KeycloakSession session;
+ protected FederationProviderModel model;
+ protected PartitionManager partitionManager;
+
+ protected static final Set<String> supportedCredentialTypes = new HashSet<String>();
+
+ static
+ {
+ supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
+ }
+
+ public LDAPFederationProvider(KeycloakSession session, FederationProviderModel model, PartitionManager partitionManager) {
+ this.session = session;
+ this.model = model;
+ this.partitionManager = partitionManager;
+ }
+
+ private ModelException convertIDMException(IdentityManagementException ie) {
+ Throwable realCause = ie;
+ while (realCause.getCause() != null) {
+ realCause = realCause.getCause();
+ }
+
+ // Use the message from the realCause
+ return new ModelException(realCause.getMessage(), ie);
+ }
+
+ public KeycloakSession getSession() {
+ return session;
+ }
+
+ public FederationProviderModel getModel() {
+ return model;
+ }
+
+ public PartitionManager getPartitionManager() {
+ return partitionManager;
+ }
+
+ @Override
+ public UserModel proxy(UserModel local) {
+ // todo
+ return new LDAPUserModelDelegate(local, this);
+ }
+
+ @Override
+ public Set<String> getSupportedCredentialTypes() {
+ return supportedCredentialTypes;
+ }
+
+ @Override
+ public UserModel addUser(RealmModel realm, UserModel user) {
+ IdentityManager identityManager = getIdentityManager();
+
+ try {
+ User picketlinkUser = new User(user.getUsername());
+ picketlinkUser.setFirstName(user.getFirstName());
+ picketlinkUser.setLastName(user.getLastName());
+ picketlinkUser.setEmail(user.getEmail());
+ identityManager.add(picketlinkUser);
+ return proxy(user);
+ } catch (IdentityManagementException ie) {
+ throw convertIDMException(ie);
+ }
+ }
+
+ @Override
+ public boolean removeUser(RealmModel realm, UserModel user) {
+ IdentityManager identityManager = getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, user.getUsername());
+ if (picketlinkUser == null) {
+ return false;
+ }
+ identityManager.remove(picketlinkUser);
+ return true;
+ } catch (IdentityManagementException ie) {
+ throw convertIDMException(ie);
+ }
+ }
+
+ @Override
+ public String getAdminPage() {
+ return null;
+ }
+
+ @Override
+ public Class getAdminClass() {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
+ throw new IllegalAccessError("Not allowed to call this");
+ }
+
+ @Override
+ public UserModel addUser(RealmModel realm, String username) {
+ throw new IllegalAccessError("Not allowed to call this");
+ }
+
+ @Override
+ public boolean removeUser(RealmModel realm, String name) {
+ throw new IllegalAccessError("Not allowed to call this");
+ }
+
+ @Override
+ public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
+ session.userStorage().addSocialLink(realm, user, socialLink);
+ }
+
+ @Override
+ public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
+ return session.userStorage().removeSocialLink(realm, user, socialProvider);
+ }
+
+ @Override
+ public UserModel getUserById(String id, RealmModel realm) {
+ return null;
+ }
+
+
+ @Override
+ public UserModel getUserByUsername(String username, RealmModel realm) {
+ IdentityManager identityManager = getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, username);
+ if (picketlinkUser == null) {
+ return null;
+ }
+
+ return importUserFromPicketlink(realm, picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw convertIDMException(ie);
+ }
+ }
+
+ public IdentityManager getIdentityManager() {
+ return partitionManager.createIdentityManager();
+ }
+
+ protected UserModel importUserFromPicketlink(RealmModel realm, User picketlinkUser) {
+ String email = (picketlinkUser.getEmail() != null && picketlinkUser.getEmail().trim().length() > 0) ? picketlinkUser.getEmail() : null;
+ UserModel imported = session.userStorage().addUser(realm, picketlinkUser.getLoginName());
+ imported.setEnabled(true);
+ imported.setEmail(email);
+ imported.setFirstName(picketlinkUser.getFirstName());
+ imported.setLastName(picketlinkUser.getLastName());
+ imported.setFederationLink(model.getId());
+ return proxy(imported);
+ }
+
+ protected User queryByEmail(IdentityManager identityManager, String email) throws IdentityManagementException {
+ List<User> agents = identityManager.createIdentityQuery(User.class)
+ .setParameter(User.EMAIL, email).getResultList();
+
+ if (agents.isEmpty()) {
+ return null;
+ } else if (agents.size() == 1) {
+ return agents.get(0);
+ } else {
+ throw new IdentityManagementException("Error - multiple Agent objects found with same login name");
+ }
+ }
+
+
+ @Override
+ public UserModel getUserByEmail(String email, RealmModel realm) {
+ IdentityManager identityManager = getIdentityManager();
+
+ try {
+ User picketlinkUser = queryByEmail(identityManager, email);
+ if (picketlinkUser == null) {
+ return null;
+ }
+
+ return importUserFromPicketlink(realm, picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw convertIDMException(ie);
+ }
+ }
+
+ @Override
+ public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
+ return null;
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public int getUsersCount(RealmModel realm) {
+ return -1;
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search, RealmModel realm) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
+ return null; //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void preRemove(RealmModel realm) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, RoleModel role) {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+
+ public boolean validPassword(String username, String password) {
+ IdentityManager identityManager = getIdentityManager();
+
+ try {
+ UsernamePasswordCredentials credential = new UsernamePasswordCredentials();
+ credential.setUsername(username);
+ credential.setPassword(new Password(password.toCharArray()));
+ identityManager.validateCredentials(credential);
+ if (credential.getStatus() == Credentials.Status.VALID) {
+ return true;
+ } else {
+ return false;
+ }
+ } catch (IdentityManagementException ie) {
+ throw convertIDMException(ie);
+ }
+ }
+
+
+ @Override
+ public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+ for (UserCredentialModel cred : input) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ return validPassword(user.getUsername(), cred.getValue());
+ } else {
+ return false; // invalid cred type
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
+ for (UserCredentialModel cred : input) {
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ return validPassword(user.getUsername(), cred.getValue());
+ } else {
+ return false; // invalid cred type
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void close() {
+ //To change body of implemented methods use File | Settings | File Templates.
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
new file mode 100755
index 0000000..6a0c57d
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java
@@ -0,0 +1,47 @@
+package org.keycloak.federation.ldap;
+
+import org.keycloak.Config;
+import org.keycloak.models.FederationProvider;
+import org.keycloak.models.FederationProviderFactory;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.PartitionManager;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LDAPFederationProviderFactory implements FederationProviderFactory {
+ public static final String PROVIDER_NAME = "ldap";
+ PartitionManagerRegistry registry;
+
+ @Override
+ public FederationProvider create(KeycloakSession session) {
+ throw new IllegalAccessError("Illegal to call this method");
+ }
+
+ @Override
+ public FederationProvider getInstance(KeycloakSession session, FederationProviderModel model) {
+ PartitionManager partition = registry.getPartitionManager(model);
+ return new LDAPFederationProvider(session, model, partition);
+ }
+
+ @Override
+ public void init(Config.Scope config) {
+ registry = new PartitionManagerRegistry();
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public String getId() {
+ return PROVIDER_NAME;
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java
new file mode 100755
index 0000000..7078d87
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPKeycloakCredentialHandler.java
@@ -0,0 +1,168 @@
+package org.keycloak.federation.ldap;
+
+import org.keycloak.models.utils.reflection.Reflections;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.config.LDAPMappingConfiguration;
+import org.picketlink.idm.credential.Credentials;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.UsernamePasswordCredentials;
+import org.picketlink.idm.ldap.internal.LDAPIdentityStore;
+import org.picketlink.idm.ldap.internal.LDAPOperationManager;
+import org.picketlink.idm.ldap.internal.LDAPPlainTextPasswordCredentialHandler;
+import org.picketlink.idm.model.Account;
+import org.picketlink.idm.model.basic.User;
+import org.picketlink.idm.query.IdentityQuery;
+import org.picketlink.idm.spi.IdentityContext;
+
+import javax.naming.NamingException;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
+import javax.naming.directory.SearchResult;
+import java.io.UnsupportedEncodingException;
+import java.util.Date;
+import java.util.List;
+
+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 LDAPKeycloakCredentialHandler extends LDAPPlainTextPasswordCredentialHandler {
+
+ // Used just in ActiveDirectory to put account into "enabled" state (aka userAccountControl=512, see http://support.microsoft.com/kb/305144/en ) after password update. If value is -1, it's ignored
+ private String userAccountControlAfterPasswordUpdate;
+
+ @Override
+ public void setup(LDAPIdentityStore store) {
+ // TODO: Don't setup it here once PLINK-508 is fixed
+ if (store.getConfig().isActiveDirectory() || Boolean.getBoolean("keycloak.ldap.ad.skipUserAccountControlAfterPasswordUpdate")) {
+ String userAccountControlProp = System.getProperty("keycloak.ldap.ad.userAccountControlAfterPasswordUpdate");
+ this.userAccountControlAfterPasswordUpdate = userAccountControlProp!=null ? userAccountControlProp : "512";
+ CREDENTIAL_LOGGER.info("Will use userAccountControl=" + userAccountControlAfterPasswordUpdate + " after password update of user in Active Directory");
+ }
+ }
+
+ // Overridden as in Keycloak, we don't have Agents
+ @Override
+ protected User 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);
+ }
+
+ @Override
+ public void update(IdentityContext context, Account account, Password password, LDAPIdentityStore store, Date effectiveDate, Date expiryDate) {
+ if (!store.getConfig().isActiveDirectory()) {
+ super.update(context, account, password, store, effectiveDate, expiryDate);
+ } else {
+ User user = (User)account;
+ LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
+ IdentityManager identityManager = getIdentityManager(context);
+ String userDN = getDNOfUser(user, identityManager, store, operationManager);
+
+ updateADPassword(userDN, new String(password.getValue()), operationManager);
+
+ if (userAccountControlAfterPasswordUpdate != null) {
+ ModificationItem[] mods = new ModificationItem[1];
+ BasicAttribute mod0 = new BasicAttribute("userAccountControl", userAccountControlAfterPasswordUpdate);
+ mods[0] = new ModificationItem(DirContext.REPLACE_ATTRIBUTE, mod0);
+ operationManager.modifyAttribute(userDN, mod0);
+ }
+ }
+ }
+
+ protected void updateADPassword(String userDN, String password, LDAPOperationManager operationManager) {
+ try {
+ // Replace the "unicdodePwd" attribute with a new value
+ // Password must be both Unicode and a quoted string
+ String newQuotedPassword = "\"" + password + "\"";
+ byte[] newUnicodePassword = newQuotedPassword.getBytes("UTF-16LE");
+ BasicAttribute unicodePwd = new BasicAttribute("unicodePwd", newUnicodePassword);
+ operationManager.modifyAttribute(userDN, unicodePwd);
+ } catch (UnsupportedEncodingException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public void validate(IdentityContext context, UsernamePasswordCredentials credentials,
+ LDAPIdentityStore store) {
+ credentials.setStatus(Credentials.Status.INVALID);
+ credentials.setValidatedAccount(null);
+
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Validating credentials [%s][%s] using identity store [%s] and credential handler [%s].", credentials.getClass(), credentials, store, this);
+ }
+
+ User account = getAccount(context, credentials.getUsername());
+
+ // If the user for the provided username cannot be found we fail validation
+ if (account != null) {
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Found account [%s] from credentials [%s].", account, credentials);
+ }
+
+ if (account.isEnabled()) {
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Account [%s] is ENABLED.", account, credentials);
+ }
+
+ char[] password = credentials.getPassword().getValue();
+
+ // String bindingDN = store.getBindingDN(account);
+
+ LDAPOperationManager operationManager = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.GET_OPERATION_MANAGER_METHOD, LDAPOperationManager.class, store);
+ String bindingDN = getDNOfUser(account, getIdentityManager(context), store, operationManager);
+
+ if (operationManager.authenticate(bindingDN, new String(password))) {
+ credentials.setValidatedAccount(account);
+ credentials.setStatus(Credentials.Status.VALID);
+ }
+ } else {
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Account [%s] is DISABLED.", account, credentials);
+ }
+ credentials.setStatus(Credentials.Status.ACCOUNT_DISABLED);
+ }
+ } else {
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Account NOT FOUND for credentials [%s][%s].", credentials.getClass(), credentials);
+ }
+ }
+
+ if (CREDENTIAL_LOGGER.isDebugEnabled()) {
+ CREDENTIAL_LOGGER.debugf("Credential [%s][%s] validated using identity store [%s] and credential handler [%s]. Status [%s]. Validated Account [%s]",
+ credentials.getClass(), credentials, store, this, credentials.getStatus(), credentials.getValidatedAccount());
+ }
+ }
+
+ // TODO: remove later... It's needed just because LDAPIdentityStore.getBindingDN, which always uses idProperty as first part of DN, but in AD it doesn't work as we may have idProperty 'sAMAccountName'
+ // but DN like: cn=John Doe,OU=foo,DC=bar
+ protected String getDNOfUser(User user, IdentityManager identityManager, LDAPIdentityStore ldapStore, LDAPOperationManager operationManager) {
+
+ LDAPMappingConfiguration ldapEntryConfig = ldapStore.getConfig().getMappingConfig(User.class);
+ IdentityQuery<User> identityQuery = identityManager.createIdentityQuery(User.class)
+ .setParameter(User.LOGIN_NAME, user.getLoginName());
+ StringBuilder filter = Reflections.invokeMethod(false, KeycloakLDAPIdentityStore.CREATE_SEARCH_FILTER_METHOD, StringBuilder.class, ldapStore, identityQuery, ldapEntryConfig);
+
+ List<SearchResult> search = null;
+ try {
+ search = operationManager.search(ldapEntryConfig.getBaseDN(), filter.toString(), ldapEntryConfig);
+ } catch (NamingException ne) {
+ throw new RuntimeException(ne);
+ }
+
+ if (search.size() > 0) {
+ SearchResult sr1 = search.get(0);
+ return sr1.getNameInNamespace();
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java
new file mode 100755
index 0000000..a2936d5
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java
@@ -0,0 +1,270 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.ApplicationModel;
+import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.ModelException;
+import org.keycloak.models.RoleModel;
+import org.keycloak.models.UserCredentialModel;
+import org.keycloak.models.UserCredentialValueModel;
+import org.keycloak.models.UserModel;
+import org.picketlink.idm.IdentityManagementException;
+import org.picketlink.idm.IdentityManager;
+import org.picketlink.idm.credential.Password;
+import org.picketlink.idm.credential.TOTPCredential;
+import org.picketlink.idm.model.basic.BasicModel;
+import org.picketlink.idm.model.basic.User;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class LDAPUserModelDelegate implements UserModel {
+ private static final Logger logger = Logger.getLogger(LDAPUserModelDelegate.class);
+
+ protected UserModel delegate;
+ protected LDAPFederationProvider provider;
+
+ public LDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
+ this.delegate = delegate;
+ this.provider = provider;
+ }
+
+ @Override
+ public String getId() {
+ return delegate.getId();
+ }
+
+ @Override
+ public void setAttribute(String name, String value) {
+ delegate.setAttribute(name, value);
+ }
+
+ @Override
+ public boolean isEmailVerified() {
+ return delegate.isEmailVerified();
+ }
+
+ @Override
+ public void removeAttribute(String name) {
+ delegate.removeAttribute(name);
+ }
+
+ @Override
+ public String getLastName() {
+ return delegate.getLastName();
+ }
+
+ @Override
+ public void setFederationLink(String link) {
+ delegate.setFederationLink(link);
+ }
+
+ @Override
+ public AuthenticationLinkModel getAuthenticationLink() {
+ return delegate.getAuthenticationLink();
+ }
+
+ @Override
+ public Map<String, String> getAttributes() {
+ return delegate.getAttributes();
+ }
+
+ @Override
+ public boolean hasRole(RoleModel role) {
+ return delegate.hasRole(role);
+ }
+
+ @Override
+ public void grantRole(RoleModel role) {
+ delegate.grantRole(role);
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ delegate.setEnabled(enabled);
+ }
+
+ @Override
+ public void removeRequiredAction(UserModel.RequiredAction action) {
+ delegate.removeRequiredAction(action);
+ }
+
+ @Override
+ public void deleteRoleMapping(RoleModel role) {
+ delegate.deleteRoleMapping(role);
+ }
+
+ @Override
+ public void setUsername(String username) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setLoginName(username);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setUsername(username);
+ }
+
+ @Override
+ public boolean isEnabled() {
+ return delegate.isEnabled();
+ }
+
+ @Override
+ public String getFirstName() {
+ return delegate.getFirstName();
+ }
+
+ @Override
+ public void setLastName(String lastName) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setLastName(lastName);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setLastName(lastName);
+ }
+
+ @Override
+ public void setEmailVerified(boolean verified) {
+ delegate.setEmailVerified(verified);
+ }
+
+ @Override
+ public void updateCredential(UserCredentialModel cred) {
+ if (!provider.getSupportedCredentialTypes().contains(cred.getType())) {
+ delegate.updateCredential(cred);
+ return;
+ }
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, getUsername());
+ if (picketlinkUser == null) {
+ logger.debugf("User '%s' doesn't exists. Skip password update", getUsername());
+ throw new IllegalStateException("User doesn't exist in LDAP storage");
+ }
+ if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
+ identityManager.updateCredential(picketlinkUser, new Password(cred.getValue().toCharArray()));
+ } else if (cred.getType().equals(UserCredentialModel.TOTP)) {
+ TOTPCredential credential = new TOTPCredential(cred.getValue());
+ credential.setDevice(cred.getDevice());
+ identityManager.updateCredential(picketlinkUser, credential);
+ }
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+
+ }
+
+ @Override
+ public void setEmail(String email) {
+ IdentityManager identityManager = provider.getIdentityManager();
+
+ try {
+ User picketlinkUser = BasicModel.getUser(identityManager, delegate.getUsername());
+ if (picketlinkUser == null) {
+ throw new IllegalStateException("User not found in LDAP storage!");
+ }
+ picketlinkUser.setEmail(email);
+ identityManager.update(picketlinkUser);
+ } catch (IdentityManagementException ie) {
+ throw new ModelException(ie);
+ }
+ delegate.setEmail(email);
+ }
+
+ @Override
+ public void addRequiredAction(UserModel.RequiredAction action) {
+ delegate.addRequiredAction(action);
+ }
+
+ @Override
+ public List<UserCredentialValueModel> getCredentialsDirectly() {
+ return delegate.getCredentialsDirectly();
+ }
+
+ @Override
+ public boolean isTotp() {
+ return delegate.isTotp();
+ }
+
+ @Override
+ public void setFirstName(String firstName) {
+ delegate.setFirstName(firstName);
+ }
+
+ @Override
+ public Set<UserModel.RequiredAction> getRequiredActions() {
+ return delegate.getRequiredActions();
+ }
+
+ @Override
+ public String getEmail() {
+ return delegate.getEmail();
+ }
+
+ @Override
+ public void setTotp(boolean totp) {
+ delegate.setTotp(totp);
+ }
+
+ @Override
+ public void setAuthenticationLink(AuthenticationLinkModel authenticationLink) {
+ delegate.setAuthenticationLink(authenticationLink);
+ }
+
+ @Override
+ public String getUsername() {
+ return delegate.getUsername();
+ }
+
+ @Override
+ public String getFederationLink() {
+ return delegate.getFederationLink();
+ }
+
+ @Override
+ public Set<RoleModel> getRealmRoleMappings() {
+ return delegate.getRealmRoleMappings();
+ }
+
+ @Override
+ public Set<RoleModel> getRoleMappings() {
+ return delegate.getRoleMappings();
+ }
+
+ @Override
+ public Set<RoleModel> getApplicationRoleMappings(ApplicationModel app) {
+ return delegate.getApplicationRoleMappings(app);
+ }
+
+ @Override
+ public String getAttribute(String name) {
+ return delegate.getAttribute(name);
+ }
+
+ @Override
+ public void updateCredentialDirectly(UserCredentialValueModel cred) {
+ delegate.updateCredentialDirectly(cred);
+ }
+}
diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java
new file mode 100755
index 0000000..860fb2e
--- /dev/null
+++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/PartitionManagerRegistry.java
@@ -0,0 +1,160 @@
+package org.keycloak.federation.ldap;
+
+import org.jboss.logging.Logger;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+import org.keycloak.models.RealmModel;
+import org.picketlink.idm.PartitionManager;
+import org.picketlink.idm.config.AbstractIdentityStoreConfiguration;
+import org.picketlink.idm.config.IdentityConfiguration;
+import org.picketlink.idm.config.IdentityConfigurationBuilder;
+import org.picketlink.idm.config.IdentityStoreConfiguration;
+import org.picketlink.idm.config.LDAPIdentityStoreConfiguration;
+import org.picketlink.idm.internal.DefaultPartitionManager;
+import org.picketlink.idm.model.basic.User;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.picketlink.common.constants.LDAPConstants.*;
+
+/**
+ * @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(FederationProviderModel model) {
+ PartitionManagerContext context = partitionManagers.get(model.getId());
+
+ // Ldap config might have changed for the realm. In this case, we must re-initialize
+ Map<String, String> config = model.getConfig();
+ if (context == null || !config.equals(context.config)) {
+ logger.infof("Creating new partition manager for the federation provider: %s, LDAP Connection URL: %s, LDAP Base DN: %s, LDAP Vendor: %s", model.getId(),
+ config.get(LDAPConstants.CONNECTION_URL), config.get(LDAPConstants.BASE_DN), config.get(LDAPConstants.VENDOR));
+ PartitionManager manager = createPartitionManager(config);
+ context = new PartitionManagerContext(config, manager);
+ partitionManagers.put(model.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();
+
+ Properties connectionProps = new Properties();
+ connectionProps.put("com.sun.jndi.ldap.connect.pool", "true");
+
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.authentication", "none simple");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.initsize", "1");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.maxsize", "10");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.prefsize", "5");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.timeout", "300000");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.protocol", "plain");
+ checkSystemProperty("com.sun.jndi.ldap.connect.pool.debug", "off");
+
+ String vendor = ldapConfig.get(LDAPConstants.VENDOR);
+
+ // RHDS is using "nsuniqueid" as unique identifier instead of "entryUUID"
+ if (vendor != null && vendor.equals(LDAPConstants.VENDOR_RHDS)) {
+ checkSystemProperty(LDAPIdentityStoreConfiguration.ENTRY_IDENTIFIER_ATTRIBUTE_NAME, "nsuniqueid");
+ }
+
+ boolean activeDirectory = vendor != null && vendor.equals(LDAPConstants.VENDOR_ACTIVE_DIRECTORY);
+
+ String ldapLoginNameMapping = ldapConfig.get(LDAPConstants.USERNAME_LDAP_ATTRIBUTE);
+ if (ldapLoginNameMapping == null) {
+ ldapLoginNameMapping = activeDirectory ? CN : UID;
+ }
+
+ // Try to compute properties based on LDAP server type, but still allow to override them through System properties TODO: Should allow better way than overriding from System properties. Perhaps init from XML?
+ ldapLoginNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.loginName", ldapLoginNameMapping, ldapLoginNameMapping, activeDirectory);
+ String ldapFirstNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.firstName", CN, "givenName", activeDirectory);
+ String ldapLastNameMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.lastName", SN, SN, activeDirectory);
+ String ldapEmailMapping = getNameOfLDAPAttribute("keycloak.ldap.idm.email", EMAIL, EMAIL, activeDirectory);
+
+ String[] userObjectClasses = getUserObjectClasses(ldapConfig);
+
+ logger.infof("LDAP Attributes mapping: loginName: %s, firstName: %s, lastName: %s, email: %s", ldapLoginNameMapping, ldapFirstNameMapping, ldapLastNameMapping, ldapEmailMapping);
+
+ // Use same mapping for User and Agent for now
+ builder
+ .named("SIMPLE_LDAP_STORE_CONFIG")
+ .stores()
+ .ldap()
+ .connectionProperties(connectionProps)
+ .addCredentialHandler(LDAPKeycloakCredentialHandler.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))
+ .activeDirectory(activeDirectory)
+ .supportAllFeatures()
+ .mapping(User.class)
+ .baseDN(ldapConfig.get(LDAPConstants.USER_DN_SUFFIX))
+ .objectClasses(userObjectClasses)
+ .attribute("loginName", ldapLoginNameMapping, true)
+ .attribute("firstName", ldapFirstNameMapping)
+ .attribute("lastName", ldapLastNameMapping)
+ .attribute("email", ldapEmailMapping);
+
+ // Workaround to override the LDAPIdentityStore with our own :/
+ List<IdentityConfiguration> identityConfigs = builder.buildAll();
+ IdentityStoreConfiguration identityStoreConfig = identityConfigs.get(0).getStoreConfiguration().get(0);
+ ((AbstractIdentityStoreConfiguration)identityStoreConfig).setIdentityStoreType(KeycloakLDAPIdentityStore.class);
+
+ return new DefaultPartitionManager(identityConfigs);
+ }
+
+ private void checkSystemProperty(String name, String defaultValue) {
+ if (System.getProperty(name) == null) {
+ System.setProperty(name, defaultValue);
+ }
+ }
+
+ private String getNameOfLDAPAttribute(String systemPropertyName, String defaultAttrName, String defaultAttrNameInActiveDirectory, boolean activeDirectory) {
+ // System property has biggest priority if available
+ String sysProperty = System.getProperty(systemPropertyName);
+ if (sysProperty != null) {
+ return sysProperty;
+ }
+
+ return activeDirectory ? defaultAttrNameInActiveDirectory : defaultAttrName;
+ }
+
+ // Parse array of strings like [ "inetOrgPerson", "organizationalPerson" ] from the string like: "inetOrgPerson, organizationalPerson"
+ private String[] getUserObjectClasses(Map<String,String> ldapConfig) {
+ String objClassesCfg = ldapConfig.get(LDAPConstants.USER_OBJECT_CLASSES);
+ String objClassesStr = (objClassesCfg != null && objClassesCfg.length() > 0) ? objClassesCfg.trim() : "inetOrgPerson, organizationalPerson";
+
+ String[] objectClasses = objClassesStr.split(",");
+
+ // Trim them
+ String[] userObjectClasses = new String[objectClasses.length];
+ for (int i=0 ; i<objectClasses.length ; i++) {
+ userObjectClasses[i] = objectClasses[i].trim();
+ }
+ return userObjectClasses;
+ }
+
+ 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;
+ }
+}
diff --git a/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory
new file mode 100755
index 0000000..b8921e9
--- /dev/null
+++ b/federation/ldap/src/main/resources/META-INF/services/org.keycloak.models.FederationProviderFactory
@@ -0,0 +1 @@
+org.keycloak.federation.ldap.LDAPFederationProviderFactory
\ No newline at end of file
federation/pom.xml 22(+22 -0)
diff --git a/federation/pom.xml b/federation/pom.xml
new file mode 100755
index 0000000..5e15f90
--- /dev/null
+++ b/federation/pom.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>keycloak-parent</artifactId>
+ <groupId>org.keycloak</groupId>
+ <version>1.0-beta-4-SNAPSHOT</version>
+ <relativePath>../pom.xml</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+ <packaging>pom</packaging>
+
+ <artifactId>keycloak-federation-parent</artifactId>
+ <name>Keycloak Federation</name>
+ <description />
+
+ <modules>
+ <module>ldap</module>
+ </modules>
+
+</project>
diff --git a/model/api/src/main/java/org/keycloak/models/entities/FederationProviderEntity.java b/model/api/src/main/java/org/keycloak/models/entities/FederationProviderEntity.java
new file mode 100755
index 0000000..0330acc
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/entities/FederationProviderEntity.java
@@ -0,0 +1,37 @@
+package org.keycloak.models.entities;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederationProviderEntity {
+ protected String id;
+ protected String providerName;
+ 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 Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
index 3c841a5..061cfe4 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java
@@ -51,6 +51,7 @@ public class RealmEntity extends AbstractIdentifiableEntity {
private List<RequiredCredentialEntity> requiredCredentials = new ArrayList<RequiredCredentialEntity>();
private List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
+ private List<FederationProviderEntity> federationProviders = new ArrayList<FederationProviderEntity>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
@@ -381,4 +382,12 @@ public class RealmEntity extends AbstractIdentifiableEntity {
public void setAdminAppId(String adminAppId) {
this.adminAppId = adminAppId;
}
+
+ public List<FederationProviderEntity> getFederationProviders() {
+ return federationProviders;
+ }
+
+ public void setFederationProviders(List<FederationProviderEntity> federationProviders) {
+ this.federationProviders = federationProviders;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
index 6db19a7..9aa0b0b 100755
--- a/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
+++ b/model/api/src/main/java/org/keycloak/models/entities/UserEntity.java
@@ -28,6 +28,7 @@ public class UserEntity extends AbstractIdentifiableEntity {
private List<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
private List<SocialLinkEntity> socialLinks;
private AuthenticationLinkEntity authenticationLink;
+ private String federationLink;
public String getUsername() {
return username;
@@ -140,5 +141,13 @@ public class UserEntity extends AbstractIdentifiableEntity {
public void setAuthenticationLink(AuthenticationLinkEntity authenticationLink) {
this.authenticationLink = authenticationLink;
}
+
+ public String getFederationLink() {
+ return federationLink;
+ }
+
+ public void setFederationLink(String federationLink) {
+ this.federationLink = federationLink;
+ }
}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationManager.java b/model/api/src/main/java/org/keycloak/models/FederationManager.java
new file mode 100755
index 0000000..8e79ba1
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationManager.java
@@ -0,0 +1,354 @@
+package org.keycloak.models;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public class FederationManager implements UserProvider {
+ protected KeycloakSession session;
+
+ public FederationManager(KeycloakSession session) {
+ this.session = session;
+ }
+
+ @Override
+ public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) {
+ UserModel user = session.userStorage().addUser(realm, id, username, addDefaultRoles);
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = session.getProvider(FederationProvider.class, federation.getProviderName());
+ return fed.addUser(realm, user);
+ }
+ return user;
+ }
+
+ protected FederationProvider getFederationProvider(FederationProviderModel model) {
+ FederationProviderFactory factory = (FederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(FederationProvider.class, model.getProviderName());
+ return factory.getInstance(session, model);
+
+ }
+
+ @Override
+ public UserModel addUser(RealmModel realm, String username) {
+ UserModel user = session.userStorage().addUser(realm, username);
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ return fed.addUser(realm, user);
+ }
+ return user;
+ }
+
+ protected FederationProvider getFederationLink(RealmModel realm, UserModel user) {
+ if (user.getFederationLink() == null) return null;
+ for (FederationProviderModel fed : realm.getFederationProviders()) {
+ if (fed.getId().equals(user.getFederationLink())) {
+ return getFederationProvider(fed);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public boolean removeUser(RealmModel realm, String name) {
+ UserModel user = session.userStorage().getUserByUsername(name, realm);
+ if (user == null) return false;
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.removeUser(realm, user);
+ }
+ return session.userStorage().removeUser(realm, name);
+
+ }
+
+ @Override
+ public void addSocialLink(RealmModel realm, UserModel user, SocialLinkModel socialLink) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ link.addSocialLink(realm, user, socialLink);
+ return;
+ }
+ session.userStorage().addSocialLink(realm, user, socialLink);
+
+ }
+
+ @Override
+ public boolean removeSocialLink(RealmModel realm, UserModel user, String socialProvider) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.removeSocialLink(realm, user, socialProvider);
+ }
+ return session.userStorage().removeSocialLink(realm, user, socialProvider);
+ }
+
+ @Override
+ public UserModel getUserById(String id, RealmModel realm) {
+ UserModel user = session.userStorage().getUserById(id, realm);
+ if (user != null) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.proxy(user);
+ }
+ return user;
+ }
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ user = fed.getUserById(id, realm);
+ if (user != null) return user;
+ }
+ return user;
+ }
+
+ @Override
+ public UserModel getUserByUsername(String username, RealmModel realm) {
+ UserModel user = session.userStorage().getUserByUsername(username, realm);
+ if (user != null) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.proxy(user);
+ }
+ return user;
+ }
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ user = fed.getUserByUsername(username, realm);
+ if (user != null) return user;
+ }
+ return user;
+ }
+
+ @Override
+ public UserModel getUserByEmail(String email, RealmModel realm) {
+ UserModel user = session.userStorage().getUserByEmail(email, realm);
+ if (user != null) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.proxy(user);
+ }
+ return user;
+ }
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ user = fed.getUserByEmail(email, realm);
+ if (user != null) return user;
+ }
+ return user;
+ }
+
+ @Override
+ public UserModel getUserBySocialLink(SocialLinkModel socialLink, RealmModel realm) {
+ UserModel user = session.userStorage().getUserBySocialLink(socialLink, realm);
+ if (user != null) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.proxy(user);
+ }
+ return user;
+ }
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ user = fed.getUserBySocialLink(socialLink, realm);
+ if (user != null) return user;
+ }
+ return user;
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm) {
+ return getUsers(realm, 0, Integer.MAX_VALUE);
+
+ }
+
+ @Override
+ public int getUsersCount(RealmModel realm) {
+ return session.userStorage().getUsersCount(realm);
+ }
+
+ @Override
+ public List<UserModel> getUsers(RealmModel realm, int firstResult, int maxResults) {
+ Map<String, UserModel> users = new HashMap<String, UserModel>();
+ List<UserModel> query = session.userStorage().getUsers(realm, firstResult, maxResults);
+ for (UserModel user : query) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ users.put(user.getUsername(), link.proxy(user));
+ } else {
+ users.put(user.getUsername(), user);
+ }
+ }
+ if (users.size() >= maxResults) {
+ List<UserModel> results = new ArrayList<UserModel>(users.size());
+ results.addAll(users.values());
+ return results;
+ }
+ List<FederationProviderModel> federationProviders = realm.getFederationProviders();
+ for (int i = federationProviders.size() - 1; i >= 0; i--) {
+ FederationProviderModel federation = federationProviders.get(i);
+ FederationProvider fed = getFederationProvider(federation);
+ query = fed.getUsers(realm, firstResult, maxResults);
+ for (UserModel user : query) users.put(user.getUsername(), user);
+ }
+ List<UserModel> results = new ArrayList<UserModel>(users.size());
+ results.addAll(users.values());
+ return results;
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search, RealmModel realm) {
+ return searchForUser(search, realm, 0, Integer.MAX_VALUE);
+ }
+
+ @Override
+ public List<UserModel> searchForUser(String search, RealmModel realm, int firstResult, int maxResults) {
+ Map<String, UserModel> users = new HashMap<String, UserModel>();
+ List<UserModel> query = session.userStorage().searchForUser(search, realm, firstResult, maxResults);
+ for (UserModel user : query) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ users.put(user.getUsername(), link.proxy(user));
+ } else {
+ users.put(user.getUsername(), user);
+ }
+ }
+ if (users.size() >= maxResults) {
+ List<UserModel> results = new ArrayList<UserModel>(users.size());
+ results.addAll(users.values());
+ return results;
+ }
+ List<FederationProviderModel> federationProviders = realm.getFederationProviders();
+ for (int i = federationProviders.size() - 1; i >= 0; i--) {
+ FederationProviderModel federation = federationProviders.get(i);
+ FederationProvider fed = getFederationProvider(federation);
+ query = fed.searchForUser(search, realm, firstResult, maxResults);
+ for (UserModel user : query) users.put(user.getUsername(), user);
+ }
+ List<UserModel> results = new ArrayList<UserModel>(users.size());
+ results.addAll(users.values());
+ return results;
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm) {
+ return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE);
+ }
+
+ @Override
+ public List<UserModel> searchForUserByAttributes(Map<String, String> attributes, RealmModel realm, int firstResult, int maxResults) {
+ Map<String, UserModel> users = new HashMap<String, UserModel>();
+ List<UserModel> query = session.userStorage().searchForUserByAttributes(attributes, realm, firstResult, maxResults);
+ for (UserModel user : query) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ users.put(user.getUsername(), link.proxy(user));
+ } else {
+ users.put(user.getUsername(), user);
+ }
+ }
+ if (users.size() >= maxResults) {
+ List<UserModel> results = new ArrayList<UserModel>(users.size());
+ results.addAll(users.values());
+ return results;
+ }
+ List<FederationProviderModel> federationProviders = realm.getFederationProviders();
+ for (int i = federationProviders.size() - 1; i >= 0; i--) {
+ FederationProviderModel federation = federationProviders.get(i);
+ FederationProvider fed = getFederationProvider(federation);
+ query = fed.searchForUserByAttributes(attributes, realm, firstResult, maxResults);
+ for (UserModel user : query) users.put(user.getUsername(), user);
+ }
+ List<UserModel> results = new ArrayList<UserModel>(users.size());
+ results.addAll(users.values());
+ return results;
+ }
+
+ @Override
+ public Set<SocialLinkModel> getSocialLinks(UserModel user, RealmModel realm) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.getSocialLinks(user, realm);
+ }
+ return session.userStorage().getSocialLinks(user, realm);
+ }
+
+ @Override
+ public SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ return link.getSocialLink(user, socialProvider, realm);
+ }
+ return session.userStorage().getSocialLink(user, socialProvider, realm);
+ }
+
+ @Override
+ public void preRemove(RealmModel realm) {
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ fed.preRemove(realm);
+ }
+ session.userStorage().preRemove(realm);
+ }
+
+ @Override
+ public void preRemove(RealmModel realm, RoleModel role) {
+ for (FederationProviderModel federation : realm.getFederationProviders()) {
+ FederationProvider fed = getFederationProvider(federation);
+ fed.preRemove(realm, role);
+ }
+ session.userStorage().preRemove(realm, role);
+ }
+
+ @Override
+ public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ if (link.getSupportedCredentialTypes().size() > 0) {
+ List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
+ List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
+ for (UserCredentialModel cred : input) {
+ if (fedCreds.contains(cred.getType())) {
+ fedCreds.add(cred);
+ } else {
+ localCreds.add(cred);
+ }
+ }
+ if (!link.validCredentials(realm, user, fedCreds)) {
+ return false;
+ }
+ return session.userStorage().validCredentials(realm, user, localCreds);
+ }
+ }
+ return session.userStorage().validCredentials(realm, user, input);
+ }
+
+ @Override
+ public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
+ FederationProvider link = getFederationLink(realm, user);
+ if (link != null) {
+ Set<String> supportedCredentialTypes = link.getSupportedCredentialTypes();
+ if (supportedCredentialTypes.size() > 0) {
+ List<UserCredentialModel> fedCreds = new ArrayList<UserCredentialModel>();
+ List<UserCredentialModel> localCreds = new ArrayList<UserCredentialModel>();
+ for (UserCredentialModel cred : input) {
+ if (supportedCredentialTypes.contains(cred.getType())) {
+ fedCreds.add(cred);
+ } else {
+ localCreds.add(cred);
+ }
+ }
+ if (!link.validCredentials(realm, user, fedCreds)) {
+ return false;
+ }
+ return session.userStorage().validCredentials(realm, user, localCreds);
+ }
+ }
+ return session.userStorage().validCredentials(realm, user, input);
+ }
+
+ @Override
+ public void close() {
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationProvider.java b/model/api/src/main/java/org/keycloak/models/FederationProvider.java
new file mode 100755
index 0000000..a70cb25
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationProvider.java
@@ -0,0 +1,17 @@
+package org.keycloak.models;
+
+import java.util.Set;
+
+/**
+ *
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface FederationProvider extends UserProvider {
+ UserModel proxy(UserModel local);
+ UserModel addUser(RealmModel realm, UserModel user);
+ boolean removeUser(RealmModel realm, UserModel user);
+ Set<String> getSupportedCredentialTypes();
+ String getAdminPage();
+ Class getAdminClass();
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationProviderFactory.java b/model/api/src/main/java/org/keycloak/models/FederationProviderFactory.java
new file mode 100755
index 0000000..214b5b5
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationProviderFactory.java
@@ -0,0 +1,11 @@
+package org.keycloak.models;
+
+import org.keycloak.provider.ProviderFactory;
+
+/**
+ * @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
+ * @version $Revision: 1 $
+ */
+public interface FederationProviderFactory extends ProviderFactory<FederationProvider> {
+ FederationProvider getInstance(KeycloakSession session, FederationProviderModel model);
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationProviderModel.java b/model/api/src/main/java/org/keycloak/models/FederationProviderModel.java
new file mode 100755
index 0000000..075d75d
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationProviderModel.java
@@ -0,0 +1,45 @@
+package org.keycloak.models;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+public class FederationProviderModel {
+
+ private String id;
+ private String providerName;
+ private Map<String, String> config = new HashMap<String, String>();
+
+ public FederationProviderModel() {};
+
+ public FederationProviderModel(String id, String providerName, Map<String, String> config) {
+ this.id = id;
+ this.providerName = providerName;
+ if (config != null) {
+ this.config.putAll(config);
+ }
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+}
diff --git a/model/api/src/main/java/org/keycloak/models/FederationSpi.java b/model/api/src/main/java/org/keycloak/models/FederationSpi.java
new file mode 100755
index 0000000..c427ec9
--- /dev/null
+++ b/model/api/src/main/java/org/keycloak/models/FederationSpi.java
@@ -0,0 +1,27 @@
+package org.keycloak.models;
+
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+import org.keycloak.provider.Spi;
+
+/**
+ * @author <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
+ */
+public class FederationSpi implements Spi {
+
+ @Override
+ public String getName() {
+ return "federation";
+ }
+
+ @Override
+ public Class<? extends Provider> getProviderClass() {
+ return FederationProvider.class;
+ }
+
+ @Override
+ public Class<? extends ProviderFactory> getProviderFactoryClass() {
+ return FederationProviderFactory.class;
+ }
+
+}
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
index 16a5a67..908093e 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakSession.java
@@ -20,6 +20,8 @@ public interface KeycloakSession {
<T extends Provider> Set<T> getAllProviders(Class<T> clazz);
+ KeycloakSessionFactory getKeycloakSessionFactory();
+
/**
* Returns a managed provider instance. Will start a provider transaction. This transaction is managed by the KeycloakSession
* transaction.
@@ -38,7 +40,19 @@ public interface KeycloakSession {
*/
UserSessionProvider sessions();
+
+
void close();
+ /**
+ * Possibly both cached and federated view of users depending on configuration.
+ *
+ * @return
+ */
UserProvider users();
+
+ /**
+ * Keycloak user storage. Non-federated, but possibly cache (if it is on) view of users.
+ */
+ UserProvider userStorage();
}
diff --git a/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java b/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
index 582b7ea..af31c09 100755
--- a/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
+++ b/model/api/src/main/java/org/keycloak/models/KeycloakSessionFactory.java
@@ -1,10 +1,18 @@
package org.keycloak.models;
+import org.keycloak.provider.Provider;
+import org.keycloak.provider.ProviderFactory;
+
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public interface KeycloakSessionFactory {
KeycloakSession create();
+
+ <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz);
+
+ <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id);
+
void close();
}
diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java
index d437c9b..3117584 100755
--- a/model/api/src/main/java/org/keycloak/models/RealmModel.java
+++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java
@@ -165,6 +165,10 @@ public interface RealmModel extends RoleContainerModel {
void setAuthenticationProviders(List<AuthenticationProviderModel> authenticationProviders);
+ List<FederationProviderModel> getFederationProviders();
+
+ void setFederationProviders(List<FederationProviderModel> providers);
+
String getLoginTheme();
void setLoginTheme(String name);
diff --git a/model/api/src/main/java/org/keycloak/models/UserModel.java b/model/api/src/main/java/org/keycloak/models/UserModel.java
index 621148e..d440c62 100755
--- a/model/api/src/main/java/org/keycloak/models/UserModel.java
+++ b/model/api/src/main/java/org/keycloak/models/UserModel.java
@@ -77,6 +77,9 @@ public interface UserModel {
Set<RoleModel> getRoleMappings();
void deleteRoleMapping(RoleModel role);
+ String getFederationLink();
+ void setFederationLink(String link);
+
diff --git a/model/api/src/main/java/org/keycloak/models/UserProvider.java b/model/api/src/main/java/org/keycloak/models/UserProvider.java
index 58436a2..b64f731 100755
--- a/model/api/src/main/java/org/keycloak/models/UserProvider.java
+++ b/model/api/src/main/java/org/keycloak/models/UserProvider.java
@@ -35,7 +35,7 @@ public interface UserProvider extends Provider {
SocialLinkModel getSocialLink(UserModel user, String socialProvider, RealmModel realm);
void preRemove(RealmModel realm);
- void preRemove(RoleModel role);
+ void preRemove(RealmModel realm, RoleModel role);
boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index c7f3e36..2272183 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -5,6 +5,7 @@ import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
import org.keycloak.models.Constants;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RequiredCredentialModel;
@@ -13,6 +14,7 @@ import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.UserSessionModel;
+import org.keycloak.representations.FederationProviderRepresentation;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
import org.keycloak.representations.idm.ClaimRepresentation;
@@ -141,6 +143,18 @@ public class ModelToRepresentation {
}
rep.setAuthenticationProviders(authProviderReps);
}
+ List<FederationProviderModel> fedProviderModels = realm.getFederationProviders();
+ if (fedProviderModels.size() > 0) {
+ List<FederationProviderRepresentation> fedProviderReps = new ArrayList<FederationProviderRepresentation>();
+ for (FederationProviderModel model : fedProviderModels) {
+ FederationProviderRepresentation fedProvRep = new FederationProviderRepresentation();
+ fedProvRep.setId(model.getId());
+ fedProvRep.setProviderName(model.getProviderName());
+ fedProvRep.setConfig(model.getConfig());
+ fedProviderReps.add(fedProvRep);
+ }
+ rep.setFederationProviders(fedProviderReps);
+ }
return rep;
}
diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index f20cb95..5aa8888 100755
--- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -7,6 +7,7 @@ import org.keycloak.models.AuthenticationLinkModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClaimMask;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
@@ -16,6 +17,7 @@ import org.keycloak.models.SocialLinkModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
+import org.keycloak.representations.FederationProviderRepresentation;
import org.keycloak.representations.idm.ApplicationRepresentation;
import org.keycloak.representations.idm.AuthenticationLinkRepresentation;
import org.keycloak.representations.idm.AuthenticationProviderRepresentation;
@@ -213,6 +215,11 @@ public class RepresentationToModel {
newRealm.setAuthenticationProviders(authProviderModels);
}
+ if (rep.getFederationProviders() != null) {
+ List<FederationProviderModel> providerModels = convertFederationProviders(rep.getFederationProviders());
+ newRealm.setFederationProviders(providerModels);
+ }
+
// create users and their role mappings and social mappings
if (rep.getUsers() != null) {
@@ -280,6 +287,11 @@ public class RepresentationToModel {
realm.setAuthenticationProviders(authProviderModels);
}
+ if (rep.getFederationProviders() != null) {
+ List<FederationProviderModel> providerModels = convertFederationProviders(rep.getFederationProviders());
+ realm.setFederationProviders(providerModels);
+ }
+
if ("GENERATE".equals(rep.getPublicKey())) {
KeycloakModelUtils.generateRealmKeys(realm);
}
@@ -303,6 +315,17 @@ public class RepresentationToModel {
return result;
}
+ private static List<FederationProviderModel> convertFederationProviders(List<FederationProviderRepresentation> providers) {
+ List<FederationProviderModel> result = new ArrayList<FederationProviderModel>();
+
+ for (FederationProviderRepresentation representation : providers) {
+ FederationProviderModel model = new FederationProviderModel(representation.getId(), representation.getProviderName(),
+ representation.getConfig());
+ result.add(model);
+ }
+ return result;
+ }
+
// Roles
public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) {
@@ -584,6 +607,7 @@ public class RepresentationToModel {
user.setEmail(userRep.getEmail());
user.setFirstName(userRep.getFirstName());
user.setLastName(userRep.getLastName());
+ user.setFederationLink(userRep.getFederationLink());
if (userRep.getAttributes() != null) {
for (Map.Entry<String, String> entry : userRep.getAttributes().entrySet()) {
user.setAttribute(entry.getKey(), entry.getValue());
diff --git a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
index 706ba92..58ccc7b 100755
--- a/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
+++ b/model/api/src/main/resources/META-INF/services/org.keycloak.provider.Spi
@@ -1,3 +1,4 @@
+org.keycloak.models.FederationSpi
org.keycloak.models.RealmSpi
org.keycloak.models.UserSessionSpi
org.keycloak.models.UserSpi
\ No newline at end of file
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java
index c405e02..68c6149 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/DefaultCacheUserProvider.java
@@ -283,7 +283,7 @@ public class DefaultCacheUserProvider implements CacheUserProvider {
}
@Override
- public void preRemove(RoleModel role) {
- getDelegate().preRemove(role);
+ public void preRemove(RealmModel realm, RoleModel role) {
+ getDelegate().preRemove(realm, role);
}
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
index 9bb18ae..507cef4 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java
@@ -2,6 +2,7 @@ package org.keycloak.models.cache.entities;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
@@ -64,6 +65,7 @@ public class CachedRealm {
private List<RequiredCredentialModel> requiredCredentials = new ArrayList<RequiredCredentialModel>();
private List<AuthenticationProviderModel> authenticationProviders = new ArrayList<AuthenticationProviderModel>();
+ private List<FederationProviderModel> federationProviders = new ArrayList<FederationProviderModel>();
private Map<String, String> smtpConfig = new HashMap<String, String>();
private Map<String, String> socialConfig = new HashMap<String, String>();
@@ -120,6 +122,7 @@ public class CachedRealm {
requiredCredentials = model.getRequiredCredentials();
authenticationProviders = model.getAuthenticationProviders();
+ federationProviders = model.getFederationProviders();
smtpConfig.putAll(model.getSmtpConfig());
socialConfig.putAll(model.getSocialConfig());
@@ -328,4 +331,7 @@ public class CachedRealm {
return auditListeners;
}
+ public List<FederationProviderModel> getFederationProviders() {
+ return federationProviders;
+ }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
index 587bf32..83f432d 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedUser.java
@@ -1,6 +1,7 @@
package org.keycloak.models.cache.entities;
import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialValueModel;
@@ -28,6 +29,7 @@ public class CachedUser {
private boolean enabled;
private boolean totp;
private AuthenticationLinkModel authenticationLink;
+ private String federationLink;
private Map<String, String> attributes = new HashMap<String, String>();
private Set<UserModel.RequiredAction> requiredActions = new HashSet<UserModel.RequiredAction>();
private Set<String> roleMappings = new HashSet<String>();
@@ -44,6 +46,7 @@ public class CachedUser {
this.credentials.addAll(user.getCredentialsDirectly());
this.enabled = user.isEnabled();
this.totp = user.isTotp();
+ this.federationLink = user.getFederationLink();
this.requiredActions.addAll(user.getRequiredActions());
this.authenticationLink = user.getAuthenticationLink();
for (RoleModel role : user.getRoleMappings()) {
@@ -102,4 +105,8 @@ public class CachedUser {
public AuthenticationLinkModel getAuthenticationLink() {
return authenticationLink;
}
+
+ public String getFederationLink() {
+ return federationLink;
+ }
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java
index c49fb1f..95f7614 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java
@@ -155,7 +155,7 @@ public class NoCacheUserProvider implements CacheUserProvider {
}
@Override
- public void preRemove(RoleModel role) {
- getDelegate().preRemove(role);
+ public void preRemove(RealmModel realm, RoleModel role) {
+ getDelegate().preRemove(realm, role);
}
}
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
index acf9fb6..b4fa0c8 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java
@@ -3,6 +3,7 @@ package org.keycloak.models.cache;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.OAuthClientModel;
import org.keycloak.models.PasswordPolicy;
@@ -609,6 +610,18 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public List<FederationProviderModel> getFederationProviders() {
+ if (updated != null) return updated.getFederationProviders();
+ return cached.getFederationProviders();
+ }
+
+ @Override
+ public void setFederationProviders(List<FederationProviderModel> providers) {
+ getDelegateForUpdate();
+ updated.setFederationProviders(providers);
+ }
+
+ @Override
public String getLoginTheme() {
if (updated != null) return updated.getLoginTheme();
return cached.getLoginTheme();
diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
index a094b64..a431ff9 100755
--- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
+++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/UserAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.models.cache;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
@@ -204,6 +205,18 @@ public class UserAdapter implements UserModel {
}
@Override
+ public String getFederationLink() {
+ if (updated != null) return updated.getFederationLink();
+ return cached.getFederationLink();
+ }
+
+ @Override
+ public void setFederationLink(String link) {
+ getDelegateForUpdate();
+ updated.setFederationLink(link);
+ }
+
+ @Override
public Set<RoleModel> getRealmRoleMappings() {
if (updated != null) return updated.getRealmRoleMappings();
Set<RoleModel> roleMappings = getRoleMappings();
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
index 1874912..f83599c 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java
@@ -138,7 +138,7 @@ public class ApplicationAdapter extends ClientAdapter implements ApplicationMode
}
if (!roleModel.getContainer().equals(this)) return false;
- session.users().preRemove(roleModel);
+ session.users().preRemove(getRealm(), roleModel);
RoleEntity role = RoleAdapter.toRoleEntity(roleModel, em);
if (!role.isApplicationRole()) return false;
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederationProviderEntity.java
new file mode 100755
index 0000000..830065c
--- /dev/null
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/FederationProviderEntity.java
@@ -0,0 +1,85 @@
+package org.keycloak.models.jpa.entities;
+
+import javax.persistence.CollectionTable;
+import javax.persistence.Column;
+import javax.persistence.ElementCollection;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.IdClass;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.MapKeyColumn;
+import javax.persistence.Table;
+import java.io.Serializable;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ * @author <a href="mailto:bburke@redhat.com">Bill Burke</a>
+ */
+@Entity
+@Table(name="FEDERATION_PROVIDER")
+public class FederationProviderEntity {
+
+ @Id
+ @Column(name="ID", length = 36)
+ protected String id;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "REALM_ID")
+ protected RealmEntity realm;
+
+ @Column(name="PROVIDER_NAME")
+ private String providerName;
+ @Column(name="PRIORITY")
+ private int priority;
+
+ @ElementCollection
+ @MapKeyColumn(name="name")
+ @Column(name="value")
+ @CollectionTable(name="FEDERATION_PROVIDER_CONFIG")
+ private Map<String, String> config;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public RealmEntity getRealm() {
+ return realm;
+ }
+
+ public void setRealm(RealmEntity realm) {
+ this.realm = realm;
+ }
+
+ public String getProviderName() {
+ return providerName;
+ }
+
+ public void setProviderName(String providerName) {
+ this.providerName = providerName;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public void setPriority(int priority) {
+ this.priority = priority;
+ }
+
+ public Map<String, String> getConfig() {
+ return config;
+ }
+
+ public void setConfig(Map<String, String> config) {
+ this.config = config;
+ }
+
+
+}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
index e77e0e5..b7af403 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java
@@ -115,6 +115,10 @@ public class RealmEntity {
@OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm")
List<AuthenticationProviderEntity> authenticationProviders = new ArrayList<AuthenticationProviderEntity>();
+ @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true)
+ @JoinTable(name="FED_PROVIDERS")
+ List<FederationProviderEntity> federationProviders = new ArrayList<FederationProviderEntity>();
+
@OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true)
@JoinTable(name="REALM_APPLICATION", joinColumns={ @JoinColumn(name="APPLICATION_ID") }, inverseJoinColumns={ @JoinColumn(name="REALM_ID") })
Collection<ApplicationEntity> applications = new ArrayList<ApplicationEntity>();
@@ -509,5 +513,12 @@ public class RealmEntity {
this.masterAdminApp = masterAdminApp;
}
+ public List<FederationProviderEntity> getFederationProviders() {
+ return federationProviders;
+ }
+
+ public void setFederationProviders(List<FederationProviderEntity> federationProviders) {
+ this.federationProviders = federationProviders;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
index 16d986a..31e139a 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java
@@ -82,6 +82,9 @@ public class UserEntity {
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
protected Collection<CredentialEntity> credentials = new ArrayList<CredentialEntity>();
+ @Column(name="federation_link")
+ protected String federationLink;
+
@OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user")
protected Collection<AuthenticationLinkEntity> authenticationLink;
@@ -198,4 +201,11 @@ public class UserEntity {
this.authenticationLink = authenticationLink;
}
+ public String getFederationLink() {
+ return federationLink;
+ }
+
+ public void setFederationLink(String federationLink) {
+ this.federationLink = federationLink;
+ }
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
index f6e2af6..24e6797 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java
@@ -136,7 +136,7 @@ public class JpaUserProvider implements UserProvider {
}
@Override
- public void preRemove(RoleModel role) {
+ public void preRemove(RealmModel realm, RoleModel role) {
em.createNamedQuery("deleteUserRoleMappingsByRole").setParameter("roleId", role.getId()).executeUpdate();
}
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
index 8a4a0b8..9c988d1 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java
@@ -3,6 +3,8 @@ package org.keycloak.models.jpa;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.jpa.entities.FederationProviderEntity;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.OAuthClientModel;
@@ -731,6 +733,64 @@ public class RealmAdapter implements RealmModel {
}
@Override
+ public List<FederationProviderModel> getFederationProviders() {
+ List<FederationProviderEntity> entities = realm.getFederationProviders();
+ List<FederationProviderEntity> copy = new ArrayList<FederationProviderEntity>();
+ for (FederationProviderEntity entity : entities) {
+ copy.add(entity);
+
+ }
+ Collections.sort(copy, new Comparator<FederationProviderEntity>() {
+
+ @Override
+ public int compare(FederationProviderEntity o1, FederationProviderEntity o2) {
+ return o1.getPriority() - o2.getPriority();
+ }
+
+ });
+ List<FederationProviderModel> result = new ArrayList<FederationProviderModel>();
+ for (FederationProviderEntity entity : copy) {
+ result.add(new FederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig()));
+ }
+
+ return result;
+ }
+
+ @Override
+ public void setFederationProviders(List<FederationProviderModel> providers) {
+ List<FederationProviderEntity> newEntities = new ArrayList<FederationProviderEntity>();
+ int counter = 1;
+ for (FederationProviderModel model : providers) {
+ FederationProviderEntity entity = new FederationProviderEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setRealm(realm);
+ entity.setProviderName(model.getProviderName());
+ entity.setConfig(model.getConfig());
+ entity.setPriority(counter++);
+ newEntities.add(entity);
+ }
+
+ // Remove all existing first
+ Collection<FederationProviderEntity> existing = realm.getFederationProviders();
+ Collection<FederationProviderEntity> copy = new ArrayList<FederationProviderEntity>(existing);
+ for (FederationProviderEntity apToRemove : copy) {
+ existing.remove(apToRemove);
+ em.remove(apToRemove);
+ }
+
+ em.flush();
+
+ // Now create all new providers
+ for (FederationProviderEntity apToAdd : newEntities) {
+ existing.add(apToAdd);
+ em.persist(apToAdd);
+ }
+
+ em.flush();
+ }
+
+
+ @Override
public RoleModel getRole(String name) {
TypedQuery<RoleEntity> query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class);
query.setParameter("name", name);
@@ -764,7 +824,7 @@ public class RealmAdapter implements RealmModel {
return false;
}
if (!role.getContainer().equals(this)) return false;
- session.users().preRemove(role);
+ session.users().preRemove(this, role);
RoleEntity roleEntity = RoleAdapter.toRoleEntity(role, em);
realm.getRoles().remove(role);
realm.getDefaultRoles().remove(role);
diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
index 6eee572..4687908 100755
--- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
+++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java
@@ -2,6 +2,7 @@ package org.keycloak.models.jpa;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationLinkModel;
+import org.keycloak.models.FederationProviderModel;
import org.keycloak.models.PasswordPolicy;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleContainerModel;
@@ -11,6 +12,7 @@ import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.jpa.entities.AuthenticationLinkEntity;
import org.keycloak.models.jpa.entities.CredentialEntity;
+import org.keycloak.models.jpa.entities.FederationProviderEntity;
import org.keycloak.models.jpa.entities.UserAttributeEntity;
import org.keycloak.models.jpa.entities.UserEntity;
import org.keycloak.models.jpa.entities.UserRequiredActionEntity;
@@ -414,6 +416,16 @@ public class UserAdapter implements UserModel {
}
@Override
+ public String getFederationLink() {
+ return user.getFederationLink();
+ }
+
+ @Override
+ public void setFederationLink(String link) {
+ user.setFederationLink(link);
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || !(o instanceof UserModel)) return false;
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
index 3171e0a..2b6ef4c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/ApplicationAdapter.java
@@ -141,7 +141,7 @@ public class ApplicationAdapter extends ClientAdapter<MongoApplicationEntity> im
@Override
public boolean removeRole(RoleModel role) {
- session.users().preRemove(role);
+ session.users().preRemove(getRealm(), role);
return getMongoStore().removeEntity(MongoRoleEntity.class, role.getId(), invocationContext);
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
index 95914ae..df60d25 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/MongoUserProvider.java
@@ -341,7 +341,7 @@ public class MongoUserProvider implements UserProvider {
}
@Override
- public void preRemove(RoleModel role) {
+ public void preRemove(RealmModel realm, RoleModel role) {
// todo not sure what to do for this
}
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
index 00e690a..713d08c 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java
@@ -7,6 +7,8 @@ import org.keycloak.connections.mongo.api.context.MongoStoreInvocationContext;
import org.keycloak.models.ApplicationModel;
import org.keycloak.models.AuthenticationProviderModel;
import org.keycloak.models.ClientModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.entities.FederationProviderEntity;
import org.keycloak.models.utils.CredentialValidation;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmProvider;
@@ -475,7 +477,7 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
public boolean removeRoleById(String id) {
RoleModel role = getRoleById(id);
if (role == null) return false;
- session.users().preRemove(role);
+ session.users().preRemove(this, role);
return getMongoStore().removeEntity(MongoRoleEntity.class, id, invocationContext);
}
@@ -793,6 +795,31 @@ public class RealmAdapter extends AbstractMongoAdapter<MongoRealmEntity> impleme
realm.setAuthenticationProviders(entities);
updateRealm();
}
+ @Override
+ public List<FederationProviderModel> getFederationProviders() {
+ List<FederationProviderEntity> entities = realm.getFederationProviders();
+ List<FederationProviderModel> result = new ArrayList<FederationProviderModel>();
+ for (FederationProviderEntity entity : entities) {
+ result.add(new FederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig()));
+ }
+
+ return result;
+ }
+
+ @Override
+ public void setFederationProviders(List<FederationProviderModel> providers) {
+ List<FederationProviderEntity> entities = new ArrayList<FederationProviderEntity>();
+ for (FederationProviderModel model : providers) {
+ FederationProviderEntity entity = new FederationProviderEntity();
+ entity.setId(KeycloakModelUtils.generateId());
+ entity.setProviderName(model.getProviderName());
+ entity.setConfig(model.getConfig());
+ entities.add(entity);
+ }
+
+ realm.setFederationProviders(entities);
+ updateRealm();
+ }
@Override
public boolean isAuditEnabled() {
diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
index ab1bd4f..1d30a5b 100755
--- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
+++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/UserAdapter.java
@@ -331,6 +331,17 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
}
@Override
+ public String getFederationLink() {
+ return user.getFederationLink();
+ }
+
+ @Override
+ public void setFederationLink(String link) {
+ user.setFederationLink(link);
+
+ }
+
+ @Override
public AuthenticationLinkModel getAuthenticationLink() {
AuthenticationLinkEntity authLinkEntity = user.getAuthenticationLink();
pom.xml 1(+1 -0)
diff --git a/pom.xml b/pom.xml
index 28af803..65cf8bd 100755
--- a/pom.xml
+++ b/pom.xml
@@ -104,6 +104,7 @@
<module>model</module>
<module>integration</module>
<module>picketlink</module>
+ <module>federation</module>
<module>services</module>
<module>social</module>
<module>forms</module>
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
index 8aae6b4..73299bf 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java
@@ -1,6 +1,8 @@
package org.keycloak.services;
+import org.keycloak.models.FederationManager;
import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakTransactionManager;
import org.keycloak.models.RealmProvider;
import org.keycloak.models.UserProvider;
@@ -26,13 +28,15 @@ public class DefaultKeycloakSession implements KeycloakSession {
private RealmProvider model;
private UserProvider userModel;
private UserSessionProvider sessionProvider;
+ private FederationManager federationManager;
public DefaultKeycloakSession(DefaultKeycloakSessionFactory factory) {
this.factory = factory;
this.transactionManager = new DefaultKeycloakTransactionManager();
+ federationManager = new FederationManager(this);
}
- private RealmProvider getModelProvider() {
+ private RealmProvider getRealmProvider() {
if (factory.getDefaultProvider(CacheRealmProvider.class) != null) {
return getProvider(CacheRealmProvider.class);
} else {
@@ -53,6 +57,20 @@ public class DefaultKeycloakSession implements KeycloakSession {
return transactionManager;
}
+ @Override
+ public KeycloakSessionFactory getKeycloakSessionFactory() {
+ return factory;
+ }
+
+ @Override
+ public UserProvider userStorage() {
+ if (userModel == null) {
+ userModel = getUserProvider();
+ }
+ return userModel;
+
+ }
+
public <T extends Provider> T getProvider(Class<T> clazz) {
Integer hash = clazz.hashCode();
T provider = (T) providers.get(hash);
@@ -95,17 +113,14 @@ public class DefaultKeycloakSession implements KeycloakSession {
@Override
public RealmProvider realms() {
if (model == null) {
- model = getModelProvider();
+ model = getRealmProvider();
}
return model;
}
@Override
public UserProvider users() {
- if (userModel == null) {
- userModel = getUserProvider();
- }
- return userModel;
+ return federationManager;
}
@Override
diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
index 560cc77..94b3ea7 100755
--- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
+++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java
@@ -74,11 +74,13 @@ public class DefaultKeycloakSessionFactory implements KeycloakSessionFactory {
return provider.get(clazz);
}
- <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
+ @Override
+ public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz) {
return getProviderFactory(clazz, provider.get(clazz));
}
- <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
+ @Override
+ public <T extends Provider> ProviderFactory<T> getProviderFactory(Class<T> clazz, String id) {
return factoriesMap.get(clazz).get(id);
}
diff --git a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
index 4d71501..6b972ce 100755
--- a/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
+++ b/services/src/main/java/org/keycloak/services/managers/AuthenticationManager.java
@@ -255,6 +255,7 @@ public class AuthenticationManager {
protected AuthenticationStatus authenticateInternal(KeycloakSession session, RealmModel realm, MultivaluedMap<String, String> formData, String username) {
UserModel user = KeycloakModelUtils.findUserByNameOrEmail(session, realm, username);
+
if (user == null) {
AuthUser authUser = AuthenticationProviderManager.getManager(realm, session).getUser(username);
if (authUser != null) {
testsuite/integration/pom.xml 5(+5 -0)
diff --git a/testsuite/integration/pom.xml b/testsuite/integration/pom.xml
index 06ae32c..3aa1ffd 100755
--- a/testsuite/integration/pom.xml
+++ b/testsuite/integration/pom.xml
@@ -99,6 +99,11 @@
</dependency>
<dependency>
<groupId>org.keycloak</groupId>
+ <artifactId>keycloak-ldap-federation</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.keycloak</groupId>
<artifactId>keycloak-undertow-adapter</artifactId>
<version>${project.version}</version>
</dependency>
diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
new file mode 100755
index 0000000..a2d0305
--- /dev/null
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java
@@ -0,0 +1,183 @@
+package org.keycloak.testsuite.forms;
+
+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.authentication.AuthProviderConstants;
+import org.keycloak.federation.ldap.LDAPFederationProviderFactory;
+import org.keycloak.model.test.LDAPEmbeddedServer;
+import org.keycloak.model.test.LDAPTestUtils;
+import org.keycloak.models.AuthenticationProviderModel;
+import org.keycloak.models.FederationProviderModel;
+import org.keycloak.models.KeycloakSession;
+import org.keycloak.models.LDAPConstants;
+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.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.pages.RegisterPage;
+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;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
+ */
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+public class FederationProvidersIntegrationTest {
+
+ 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(manager.getSession(), appRealm, "mary", "mary@test.com", "password-app");
+ addUser(manager.getSession(), adminstrationRealm, "mary-admin", "mary@admin.com", "password-admin");
+
+ LDAPEmbeddedServer ldapServer = ldapRule.getEmbeddedServer();
+ Map<String,String> ldapConfig = new HashMap<String,String>();
+ ldapConfig.put(LDAPConstants.CONNECTION_URL, ldapServer.getConnectionUrl());
+ ldapConfig.put(LDAPConstants.BASE_DN, ldapServer.getBaseDn());
+ ldapConfig.put(LDAPConstants.BIND_DN, ldapServer.getBindDn());
+ ldapConfig.put(LDAPConstants.BIND_CREDENTIAL, ldapServer.getBindCredential());
+ ldapConfig.put(LDAPConstants.USER_DN_SUFFIX, ldapServer.getUserDnSuffix());
+ ldapConfig.put(LDAPConstants.VENDOR, ldapServer.getVendor());
+
+
+ FederationProviderModel ldapProvider = new FederationProviderModel(null, LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig);
+ appRealm.setFederationProviders(Arrays.asList(ldapProvider));
+
+ // Configure LDAP
+ ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm);
+ LDAPTestUtils.setLdapPassword(session, appRealm, "johnkeycloak", "password");
+ }
+ });
+
+ @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 RegisterPage registerPage;
+
+ @WebResource
+ protected LoginPage loginPage;
+
+ @WebResource
+ protected AccountUpdateProfilePage profilePage;
+
+ @WebResource
+ protected AccountPasswordPage changePasswordPage;
+
+ private static UserModel addUser(KeycloakSession session, RealmModel realm, String username, String email, String password) {
+ UserModel user = session.users().addUser(realm, username);
+ user.setEmail(email);
+ user.setEnabled(true);
+
+ UserCredentialModel creds = new UserCredentialModel();
+ creds.setType(CredentialRepresentation.PASSWORD);
+ creds.setValue(password);
+
+ user.updateCredential(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 loginLdap() {
+ loginPage.open();
+ loginPage.login("johnkeycloak", "password");
+
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE));
+
+ profilePage.open();
+ Assert.assertEquals("John", profilePage.getFirstName());
+ Assert.assertEquals("Doe", profilePage.getLastName());
+ Assert.assertEquals("john@email.org", profilePage.getEmail());
+ }
+
+ @Test
+ public void passwordChangeLdap() throws Exception {
+ changePasswordPage.open();
+ loginPage.login("johnkeycloak", "password");
+ changePasswordPage.changePassword("password", "new-password", "new-password");
+
+ Assert.assertEquals("Your password has been updated", profilePage.getSuccess());
+
+ changePasswordPage.logout();
+
+ loginPage.open();
+ loginPage.login("johnkeycloak", "password");
+ Assert.assertEquals("Invalid username or password.", loginPage.getError());
+
+ loginPage.open();
+ loginPage.login("johnkeycloak", "new-password");
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ }
+
+ @Test
+ public void registerExistingLdapUser() {
+ loginPage.open();
+ loginPage.clickRegister();
+ registerPage.assertCurrent();
+
+ registerPage.register("firstName", "lastName", "email", "existing", "password", "password");
+
+ registerPage.assertCurrent();
+ Assert.assertEquals("Username already exists", registerPage.getError());
+ }
+
+ @Test
+ public void registerUserLdapSuccess() {
+ loginPage.open();
+ loginPage.clickRegister();
+ registerPage.assertCurrent();
+
+ registerPage.register("firstName", "lastName", "email2", "registerUserSuccess2", "password", "password");
+ Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType());
+ }
+}