federation iteration

This commit is contained in:
Bill Burke 2014-07-23 10:21:25 -04:00
parent b999c1e5b1
commit 148d494905
49 changed files with 2133 additions and 143 deletions

View file

@ -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>

View file

@ -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>

View file

@ -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();
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -308,6 +308,7 @@ public class ExportUtils {
credReps.add(credRep);
}
userRep.setCredentials(credReps);
userRep.setFederationLink(user.getFederationLink());
return userRep;
}

88
federation/ldap/pom.xml Executable file
View file

@ -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>

View file

@ -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;
}
}

View file

@ -0,0 +1,324 @@
package org.keycloak.federation.ldap;
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.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 {
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 local;
}
@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.setEmail(email);
imported.setFirstName(picketlinkUser.getFirstName());
imported.setLastName(picketlinkUser.getLastName());
imported.setFederationLink(model.getId());
return 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.
}
}

View file

@ -0,0 +1,46 @@
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 {
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 "ldap";
}
}

View file

@ -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;
}
}
}

View file

@ -0,0 +1,203 @@
package org.keycloak.federation.ldap;
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.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 {
protected UserModel delegate;
protected LDAPFederationProvider provider;
public String getId() {
return delegate.getId();
}
public void setAttribute(String name, String value) {
delegate.setAttribute(name, value);
}
public boolean isEmailVerified() {
return delegate.isEmailVerified();
}
public void removeAttribute(String name) {
delegate.removeAttribute(name);
}
public String getLastName() {
return delegate.getLastName();
}
public void setFederationLink(String link) {
delegate.setFederationLink(link);
}
public AuthenticationLinkModel getAuthenticationLink() {
return delegate.getAuthenticationLink();
}
public Map<String, String> getAttributes() {
return delegate.getAttributes();
}
public boolean hasRole(RoleModel role) {
return delegate.hasRole(role);
}
public void grantRole(RoleModel role) {
delegate.grantRole(role);
}
public void setEnabled(boolean enabled) {
delegate.setEnabled(enabled);
}
public void removeRequiredAction(UserModel.RequiredAction action) {
delegate.removeRequiredAction(action);
}
public void deleteRoleMapping(RoleModel role) {
delegate.deleteRoleMapping(role);
}
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);
}
public boolean isEnabled() {
return delegate.isEnabled();
}
public String getFirstName() {
return delegate.getFirstName();
}
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);
}
public void setEmailVerified(boolean verified) {
delegate.setEmailVerified(verified);
}
public void updateCredential(UserCredentialModel cred) {
delegate.updateCredential(cred);
}
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);
}
public void addRequiredAction(UserModel.RequiredAction action) {
delegate.addRequiredAction(action);
}
public List<UserCredentialValueModel> getCredentialsDirectly() {
return delegate.getCredentialsDirectly();
}
public boolean isTotp() {
return delegate.isTotp();
}
public void setFirstName(String firstName) {
delegate.setFirstName(firstName);
}
public Set<UserModel.RequiredAction> getRequiredActions() {
return delegate.getRequiredActions();
}
public String getEmail() {
return delegate.getEmail();
}
public void setTotp(boolean totp) {
delegate.setTotp(totp);
}
public void setAuthenticationLink(AuthenticationLinkModel authenticationLink) {
delegate.setAuthenticationLink(authenticationLink);
}
public String getUsername() {
return delegate.getUsername();
}
public String getFederationLink() {
return delegate.getFederationLink();
}
public Set<RoleModel> getRealmRoleMappings() {
return delegate.getRealmRoleMappings();
}
public Set<RoleModel> getRoleMappings() {
return delegate.getRoleMappings();
}
public Set<RoleModel> getApplicationRoleMappings(ApplicationModel app) {
return delegate.getApplicationRoleMappings(app);
}
public String getAttribute(String name) {
return delegate.getAttribute(name);
}
public void updateCredentialDirectly(UserCredentialValueModel cred) {
delegate.updateCredentialDirectly(cred);
}
}

View file

@ -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;
}
}

22
federation/pom.xml Executable file
View file

@ -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>

View file

@ -0,0 +1,349 @@
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;
@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 = session.getProvider(FederationProvider.class, federation.getProviderName());
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 = session.getProvider(FederationProvider.class, federation.getProviderName());
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 = session.getProvider(FederationProvider.class, federation.getProviderName());
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(); 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(); 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(); 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 = session.getProvider(FederationProvider.class, federation.getProviderName());
fed.preRemove(realm);
}
session.userStorage().preRemove(realm);
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
for (FederationProviderModel federation : realm.getFederationProviders()) {
FederationProvider fed = session.getProvider(FederationProvider.class, federation.getProviderName());
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) {
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 void close() {
}
}

View file

@ -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();
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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();
}

View file

@ -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();
}

View file

@ -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);

View file

@ -77,6 +77,9 @@ public interface UserModel {
Set<RoleModel> getRoleMappings();
void deleteRoleMapping(RoleModel role);
String getFederationLink();
void setFederationLink(String link);

View file

@ -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);

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;
}

View file

@ -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());

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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;
@ -608,6 +609,18 @@ public class RealmAdapter implements RealmModel {
updated.setAuthenticationProviders(authenticationProviders);
}
@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();

View file

@ -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;
@ -203,6 +204,18 @@ public class UserAdapter implements UserModel {
updated.setAuthenticationLink(authenticationLink);
}
@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();

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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();
}

View file

@ -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;
@ -730,6 +732,64 @@ public class RealmAdapter implements RealmModel {
em.flush();
}
@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);
@ -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);

View file

@ -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;
@ -413,6 +415,16 @@ public class UserAdapter implements UserModel {
em.flush();
}
@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;

View file

@ -1,127 +1,127 @@
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>
*/
@Entity
@Table(name="AUTH_PROVIDER_ENTITY")
@IdClass(AuthenticationProviderEntity.Key.class)
public class AuthenticationProviderEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "REALM_ID")
protected RealmEntity realm;
@Id
@Column(name="PROVIDER_NAME")
private String providerName;
@Column(name="PASSWORD_UPDATE_SUPPORTED")
private boolean passwordUpdateSupported;
@Column(name="PRIORITY")
private int priority;
@ElementCollection
@MapKeyColumn(name="name")
@Column(name="value")
@CollectionTable(name="AUTH_PROVIDER_CONFIG")
private Map<String, String> config;
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 boolean isPasswordUpdateSupported() {
return passwordUpdateSupported;
}
public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
this.passwordUpdateSupported = passwordUpdateSupported;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public Map<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
public static class Key implements Serializable {
protected RealmEntity realm;
protected String providerName;
public Key() {
}
public Key(RealmEntity realm, String providerName) {
this.realm = realm;
this.providerName = providerName;
}
public RealmEntity getRealm() {
return realm;
}
public String getProviderName() {
return providerName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (providerName != null ? !providerName.equals(key.providerName) : key.providerName != null) return false;
if (realm != null ? !realm.getId().equals(key.realm != null ? key.realm.getId() : null) : key.realm != null) return false;
return true;
}
@Override
public int hashCode() {
int result = realm != null ? realm.getId().hashCode() : 0;
result = 31 * result + (providerName != null ? providerName.hashCode() : 0);
return result;
}
}
}
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>
*/
@Entity
@Table(name="AUTH_PROVIDER")
@IdClass(AuthenticationProviderEntity.Key.class)
public class AuthenticationProviderEntity {
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "REALM_ID")
protected RealmEntity realm;
@Id
@Column(name="PROVIDER_NAME")
private String providerName;
@Column(name="PASSWORD_UPDATE_SUPPORTED")
private boolean passwordUpdateSupported;
@Column(name="PRIORITY")
private int priority;
@ElementCollection
@MapKeyColumn(name="name")
@Column(name="value")
@CollectionTable(name="AUTH_PROVIDER_CONFIG")
private Map<String, String> config;
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 boolean isPasswordUpdateSupported() {
return passwordUpdateSupported;
}
public void setPasswordUpdateSupported(boolean passwordUpdateSupported) {
this.passwordUpdateSupported = passwordUpdateSupported;
}
public int getPriority() {
return priority;
}
public void setPriority(int priority) {
this.priority = priority;
}
public Map<String, String> getConfig() {
return config;
}
public void setConfig(Map<String, String> config) {
this.config = config;
}
public static class Key implements Serializable {
protected RealmEntity realm;
protected String providerName;
public Key() {
}
public Key(RealmEntity realm, String providerName) {
this.realm = realm;
this.providerName = providerName;
}
public RealmEntity getRealm() {
return realm;
}
public String getProviderName() {
return providerName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key) o;
if (providerName != null ? !providerName.equals(key.providerName) : key.providerName != null) return false;
if (realm != null ? !realm.getId().equals(key.realm != null ? key.realm.getId() : null) : key.realm != null) return false;
return true;
}
@Override
public int hashCode() {
int result = realm != null ? realm.getId().hashCode() : 0;
result = 31 * result + (providerName != null ? providerName.hashCode() : 0);
return result;
}
}
}

View file

@ -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;
}
}

View file

@ -117,6 +117,10 @@ public class RealmEntity {
@JoinTable(name="AUTH_PROVIDERS")
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)
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;
}
}

View file

@ -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;
}
}

View file

@ -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);
}

View file

@ -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
}

View file

@ -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() {

View file

@ -330,6 +330,17 @@ public class UserAdapter extends AbstractMongoAdapter<MongoUserEntity> implement
return result;
}
@Override
public String getFederationLink() {
return user.getFederationLink();
}
@Override
public void setFederationLink(String link) {
user.setFederationLink(link);
}
@Override
public AuthenticationLinkModel getAuthenticationLink() {
AuthenticationLinkEntity authLinkEntity = user.getAuthenticationLink();

View file

@ -103,6 +103,7 @@
<module>model</module>
<module>integration</module>
<module>picketlink</module>
<module>federation</module>
<module>services</module>
<module>social</module>
<module>forms</module>

View file

@ -1,6 +1,7 @@
package org.keycloak.services;
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;
@ -32,7 +33,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
this.transactionManager = new DefaultKeycloakTransactionManager();
}
private RealmProvider getModelProvider() {
private RealmProvider getRealmProvider() {
if (factory.getDefaultProvider(CacheRealmProvider.class) != null) {
return getProvider(CacheRealmProvider.class);
} else {
@ -53,6 +54,16 @@ public class DefaultKeycloakSession implements KeycloakSession {
return transactionManager;
}
@Override
public KeycloakSessionFactory getKeycloakSessionFactory() {
return factory;
}
@Override
public UserProvider userStorage() {
return null;
}
public <T extends Provider> T getProvider(Class<T> clazz) {
Integer hash = clazz.hashCode();
T provider = (T) providers.get(hash);
@ -95,7 +106,7 @@ public class DefaultKeycloakSession implements KeycloakSession {
@Override
public RealmProvider realms() {
if (model == null) {
model = getModelProvider();
model = getRealmProvider();
}
return model;
}

View file

@ -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);
}