diff --git a/authentication/authentication-picketlink/pom.xml b/authentication/authentication-picketlink/pom.xml index 3ca1a10a88..37ae937785 100755 --- a/authentication/authentication-picketlink/pom.xml +++ b/authentication/authentication-picketlink/pom.xml @@ -4,7 +4,7 @@ keycloak-authentication-parent org.keycloak 1.0-beta-4-SNAPSHOT - ../pom.xml + ../../pom.xml 4.0.0 diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index 6c0efe9c23..0581b717b5 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 @@ org.keycloak.models.jpa.entities.RealmEntity org.keycloak.models.jpa.entities.RequiredCredentialEntity org.keycloak.models.jpa.entities.AuthenticationProviderEntity + org.keycloak.models.jpa.entities.FederationProviderEntity org.keycloak.models.jpa.entities.RoleEntity org.keycloak.models.jpa.entities.SocialLinkEntity org.keycloak.models.jpa.entities.AuthenticationLinkEntity 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 0000000000..e14f96e18b --- /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 Marek Posolda + */ +public class FederationProviderRepresentation { + + private String id; + private String providerName; + private Map config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + + public Map getConfig() { + return config; + } + + public void setConfig(Map 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 5a65329811..584e6392db 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 smtpServer; protected Map ldapServer; protected List authenticationProviders; + protected List federationProviders; protected String loginTheme; protected String accountTheme; protected String adminTheme; @@ -467,4 +470,12 @@ public class RealmRepresentation { public void setAuditListeners(List auditListeners) { this.auditListeners = auditListeners; } + + public List getFederationProviders() { + return federationProviders; + } + + public void setFederationProviders(List 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 abc78466f2..257898373d 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 attributes; protected List credentials; protected List requiredActions; @@ -170,4 +171,12 @@ public class UserRepresentation { public void setApplicationRoles(Map> 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 dd3c5dd3cf..4c1cc5befc 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; } diff --git a/federation/ldap/pom.xml b/federation/ldap/pom.xml new file mode 100755 index 0000000000..5f8f887c99 --- /dev/null +++ b/federation/ldap/pom.xml @@ -0,0 +1,88 @@ + + + keycloak-authentication-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../../pom.xml + + 4.0.0 + + keycloak-ldap-federation + Keycloak LDAP Federation + + + + + org.keycloak + keycloak-core + ${project.version} + provided + + + org.keycloak + keycloak-model-api + ${project.version} + provided + + + org.jboss.resteasy + resteasy-jaxrs + provided + + + log4j + log4j + + + org.slf4j + slf4j-api + + + org.slf4j + slf4j-simple + + + + + org.jboss.logging + jboss-logging + provided + + + + org.picketlink + picketlink-common + provided + + + org.picketlink + picketlink-idm-api + provided + + + org.picketlink + picketlink-idm-impl + provided + + + org.picketlink + picketlink-idm-simple-schema + provided + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + ${maven.compiler.source} + ${maven.compiler.target} + + + + + + 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 0000000000..1ff6c119a1 --- /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 Marek Posolda + */ +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 0000000000..dcaa5fa011 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -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 Bill Burke + * @version $Revision: 1 $ + */ +public class LDAPFederationProvider implements FederationProvider { + protected KeycloakSession session; + protected FederationProviderModel model; + protected PartitionManager partitionManager; + + protected static final Set supportedCredentialTypes = new HashSet(); + + 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 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 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 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 getUsers(RealmModel realm, int firstResult, int maxResults) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public List 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 searchForUserByAttributes(Map attributes, RealmModel realm) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults) { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + @Override + public Set 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 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 0000000000..ecc3b34ac7 --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java @@ -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 Bill Burke + * @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"; + } +} 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 0000000000..7078d87494 --- /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 Marek Posolda + */ +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 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 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 0000000000..a96c6bdedc --- /dev/null +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUserModelDelegate.java @@ -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 Bill Burke + * @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 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 getCredentialsDirectly() { + return delegate.getCredentialsDirectly(); + } + + public boolean isTotp() { + return delegate.isTotp(); + } + + public void setFirstName(String firstName) { + delegate.setFirstName(firstName); + } + + public Set 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 getRealmRoleMappings() { + return delegate.getRealmRoleMappings(); + } + + public Set getRoleMappings() { + return delegate.getRoleMappings(); + } + + public Set getApplicationRoleMappings(ApplicationModel app) { + return delegate.getApplicationRoleMappings(app); + } + + public String getAttribute(String name) { + return delegate.getAttribute(name); + } + + 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 0000000000..860fb2e753 --- /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 Marek Posolda + */ +public class PartitionManagerRegistry { + + private static final Logger logger = Logger.getLogger(PartitionManagerRegistry.class); + + private Map partitionManagers = new ConcurrentHashMap(); + + 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 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 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 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 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 config, PartitionManager manager) { + this.config = config; + this.partitionManager = manager; + } + + private Map config; + private PartitionManager partitionManager; + } +} diff --git a/federation/pom.xml b/federation/pom.xml new file mode 100755 index 0000000000..5e15f90490 --- /dev/null +++ b/federation/pom.xml @@ -0,0 +1,22 @@ + + + + keycloak-parent + org.keycloak + 1.0-beta-4-SNAPSHOT + ../pom.xml + + 4.0.0 + pom + + keycloak-federation-parent + Keycloak Federation + + + + ldap + + + 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 0000000000..7c5bbc0e3b --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/FederationManager.java @@ -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 Bill Burke + * @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 getUsers(RealmModel realm) { + return getUsers(realm, 0, Integer.MAX_VALUE); + + } + + @Override + public int getUsersCount(RealmModel realm) { + return session.userStorage().getUsersCount(realm); + } + + @Override + public List getUsers(RealmModel realm, int firstResult, int maxResults) { + Map users = new HashMap(); + List 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 results = new ArrayList(users.size()); + results.addAll(users.values()); + return results; + } + List 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 results = new ArrayList(users.size()); + results.addAll(users.values()); + return results; + } + + @Override + public List searchForUser(String search, RealmModel realm) { + return searchForUser(search, realm, 0, Integer.MAX_VALUE); + } + + @Override + public List searchForUser(String search, RealmModel realm, int firstResult, int maxResults) { + Map users = new HashMap(); + List 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 results = new ArrayList(users.size()); + results.addAll(users.values()); + return results; + } + List 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 results = new ArrayList(users.size()); + results.addAll(users.values()); + return results; + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm) { + return searchForUserByAttributes(attributes, realm, 0, Integer.MAX_VALUE); + } + + @Override + public List searchForUserByAttributes(Map attributes, RealmModel realm, int firstResult, int maxResults) { + Map users = new HashMap(); + List 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 results = new ArrayList(users.size()); + results.addAll(users.values()); + return results; + } + List 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 results = new ArrayList(users.size()); + results.addAll(users.values()); + return results; + } + + @Override + public Set 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 input) { + FederationProvider link = getFederationLink(realm, user); + if (link != null) { + if (link.getSupportedCredentialTypes().size() > 0) { + List fedCreds = new ArrayList(); + List localCreds = new ArrayList(); + 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 fedCreds = new ArrayList(); + List localCreds = new ArrayList(); + 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() { + } +} 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 0000000000..a70cb252b6 --- /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 Bill Burke + * @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 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 0000000000..214b5b565c --- /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 Bill Burke + * @version $Revision: 1 $ + */ +public interface FederationProviderFactory extends ProviderFactory { + 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 0000000000..075d75d4e2 --- /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 Marek Posolda + * @author Bill Burke + */ +public class FederationProviderModel { + + private String id; + private String providerName; + private Map config = new HashMap(); + + public FederationProviderModel() {}; + + public FederationProviderModel(String id, String providerName, Map 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 getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} 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 16a5a6798d..908093ef21 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 { Set getAllProviders(Class 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 582b7eaf93..af31c094d7 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 Bill Burke * @version $Revision: 1 $ */ public interface KeycloakSessionFactory { KeycloakSession create(); + + ProviderFactory getProviderFactory(Class clazz); + + ProviderFactory getProviderFactory(Class 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 d437c9bc78..3117584e1c 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 authenticationProviders); + List getFederationProviders(); + + void setFederationProviders(List 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 621148e09e..d440c62a19 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 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 58436a262f..b64f73128a 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 input); boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input); 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 0000000000..0330acc9e8 --- /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 Bill Burke + * @version $Revision: 1 $ + */ +public class FederationProviderEntity { + protected String id; + protected String providerName; + private Map config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getProviderName() { + return providerName; + } + + public void setProviderName(String providerName) { + this.providerName = providerName; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map 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 3c841a5d5a..061cfe42a0 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 requiredCredentials = new ArrayList(); private List authenticationProviders = new ArrayList(); + private List federationProviders = new ArrayList(); private Map smtpConfig = new HashMap(); private Map socialConfig = new HashMap(); @@ -381,4 +382,12 @@ public class RealmEntity extends AbstractIdentifiableEntity { public void setAdminAppId(String adminAppId) { this.adminAppId = adminAppId; } + + public List getFederationProviders() { + return federationProviders; + } + + public void setFederationProviders(List 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 6db19a7609..9aa0b0b576 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 credentials = new ArrayList(); private List 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/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index c7f3e36e71..227218343d 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 fedProviderModels = realm.getFederationProviders(); + if (fedProviderModels.size() > 0) { + List fedProviderReps = new ArrayList(); + 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 f20cb9521b..5aa8888eff 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 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 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 convertFederationProviders(List providers) { + List result = new ArrayList(); + + 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 entry : userRep.getAttributes().entrySet()) { user.setAttribute(entry.getKey(), entry.getValue()); 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 c405e0288b..68c61494ed 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/NoCacheUserProvider.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/NoCacheUserProvider.java index c49fb1f88b..95f7614932 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 acf9fb6651..b4fa0c84a4 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; @@ -608,6 +609,18 @@ public class RealmAdapter implements RealmModel { updated.setAuthenticationProviders(authenticationProviders); } + @Override + public List getFederationProviders() { + if (updated != null) return updated.getFederationProviders(); + return cached.getFederationProviders(); + } + + @Override + public void setFederationProviders(List providers) { + getDelegateForUpdate(); + updated.setFederationProviders(providers); + } + @Override public String getLoginTheme() { if (updated != null) return updated.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 a094b64b53..a431ff95c0 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; @@ -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 getRealmRoleMappings() { if (updated != null) return updated.getRealmRoleMappings(); 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 9bb18ae6f9..507cef49f9 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 requiredCredentials = new ArrayList(); private List authenticationProviders = new ArrayList(); + private List federationProviders = new ArrayList(); private Map smtpConfig = new HashMap(); private Map socialConfig = new HashMap(); @@ -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 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 587bf325d1..83f432d978 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 attributes = new HashMap(); private Set requiredActions = new HashSet(); private Set roleMappings = new HashSet(); @@ -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/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/ApplicationAdapter.java index 966e6c9bea..fda59caef5 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/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index f6e2af6e55..24e6797ee3 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 8a4a0b80c8..9c988d19cc 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; @@ -730,6 +732,64 @@ public class RealmAdapter implements RealmModel { em.flush(); } + @Override + public List getFederationProviders() { + List entities = realm.getFederationProviders(); + List copy = new ArrayList(); + for (FederationProviderEntity entity : entities) { + copy.add(entity); + + } + Collections.sort(copy, new Comparator() { + + @Override + public int compare(FederationProviderEntity o1, FederationProviderEntity o2) { + return o1.getPriority() - o2.getPriority(); + } + + }); + List result = new ArrayList(); + for (FederationProviderEntity entity : copy) { + result.add(new FederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig())); + } + + return result; + } + + @Override + public void setFederationProviders(List providers) { + List newEntities = new ArrayList(); + 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 existing = realm.getFederationProviders(); + Collection copy = new ArrayList(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 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); 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 6eee572629..4687908a33 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; @@ -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; diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java index f3d9d3b717..d8e3e6100b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/AuthenticationProviderEntity.java @@ -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 Marek Posolda - */ -@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 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 getConfig() { - return config; - } - - public void setConfig(Map 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 Marek Posolda + */ +@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 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 getConfig() { + return config; + } + + public void setConfig(Map 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; + } + } + +} 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 0000000000..830065c193 --- /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 Marek Posolda + * @author Bill Burke + */ +@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 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 getConfig() { + return config; + } + + public void setConfig(Map 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 d16543b1c1..c81fc8d659 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 @@ -117,6 +117,10 @@ public class RealmEntity { @JoinTable(name="AUTH_PROVIDERS") List authenticationProviders = new ArrayList(); + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true) + @JoinTable(name="FED_PROVIDERS") + List federationProviders = new ArrayList(); + @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true) Collection applications = new ArrayList(); @@ -509,5 +513,12 @@ public class RealmEntity { this.masterAdminApp = masterAdminApp; } + public List getFederationProviders() { + return federationProviders; + } + + public void setFederationProviders(List 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 376bceadae..86acf1e8e9 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 credentials = new ArrayList(); + @Column(name="federation_link") + protected String federationLink; + @OneToMany(cascade = CascadeType.REMOVE, orphanRemoval = true, mappedBy="user") protected Collection 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/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 606ebd3a75..e7bb15cb85 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 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 95914aec77..df60d25d33 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 00e690a076..713d08c983 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 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 impleme realm.setAuthenticationProviders(entities); updateRealm(); } + @Override + public List getFederationProviders() { + List entities = realm.getFederationProviders(); + List result = new ArrayList(); + for (FederationProviderEntity entity : entities) { + result.add(new FederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig())); + } + + return result; + } + + @Override + public void setFederationProviders(List providers) { + List entities = new ArrayList(); + 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 ab1bd4fd44..1d30a5ba5b 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 @@ -330,6 +330,17 @@ public class UserAdapter extends AbstractMongoAdapter 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(); diff --git a/pom.xml b/pom.xml index bedff869dd..656f16e531 100755 --- a/pom.xml +++ b/pom.xml @@ -103,6 +103,7 @@ model integration picketlink + federation services social forms diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java index 8aae6b4895..09ed19c607 100755 --- a/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java +++ b/services/src/main/java/org/keycloak/services/DefaultKeycloakSession.java @@ -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 getProvider(Class 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; } diff --git a/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java b/services/src/main/java/org/keycloak/services/DefaultKeycloakSessionFactory.java index 560cc77ae4..94b3ea7dad 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); } - ProviderFactory getProviderFactory(Class clazz) { + @Override + public ProviderFactory getProviderFactory(Class clazz) { return getProviderFactory(clazz, provider.get(clazz)); } - ProviderFactory getProviderFactory(Class clazz, String id) { + @Override + public ProviderFactory getProviderFactory(Class clazz, String id) { return factoriesMap.get(clazz).get(id); }