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 index c42f74c43a..5ad113600d 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -7,7 +7,6 @@ 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; @@ -28,12 +27,14 @@ import java.util.Map; import java.util.Set; /** + * @author Marek Posolda * @author Bill Burke * @version $Revision: 1 $ */ public class LDAPFederationProvider implements UserFederationProvider { private static final Logger logger = Logger.getLogger(LDAPFederationProvider.class); public static final String LDAP_ID = "LDAP_ID"; + public static final String SYNC_REGISTRATIONS = "syncRegistrations"; protected KeycloakSession session; protected UserFederationProviderModel model; @@ -86,12 +87,13 @@ public class LDAPFederationProvider implements UserFederationProvider { } @Override - public boolean isRegistrationSupported() { - return true; + public boolean synchronizeRegistrations() { + return "true".equalsIgnoreCase(model.getConfig().get(SYNC_REGISTRATIONS)); } @Override public UserModel register(RealmModel realm, UserModel user) { + if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server"); IdentityManager identityManager = getIdentityManager(); try { @@ -100,6 +102,7 @@ public class LDAPFederationProvider implements UserFederationProvider { picketlinkUser.setLastName(user.getLastName()); picketlinkUser.setEmail(user.getEmail()); identityManager.add(picketlinkUser); + user.setAttribute(LDAP_ID, picketlinkUser.getId()); return proxy(user); } catch (IdentityManagementException ie) { throw convertIDMException(ie); 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 index 155c2a0d66..969d9b2363 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProviderFactory.java @@ -11,6 +11,7 @@ import java.util.Collections; import java.util.List; /** + * @author Marek Posolda * @author Bill Burke * @version $Revision: 1 $ */ diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js index 0161019b13..2b17931e07 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/js/controllers/users.js @@ -434,7 +434,6 @@ module.controller('GenericUserFederationCtrl', function($scope, $location, Notif }); - module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, realm, instance, UserFederationInstances, RealmLDAPConnectionTester) { console.log('LDAPCtrl'); @@ -445,6 +444,9 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, $scope.instance.providerName = "ldap"; $scope.instance.config = {}; $scope.instance.priority = 0; + $scope.syncRegistrations = false; + } else { + $scope.syncRegistrations = instance.config.syncRegistrations && instance.config.syncRegistrations == "true"; } $scope.ldapVendors = [ @@ -464,6 +466,14 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, $scope.lastVendor = $scope.instance.config.vendor; + $scope.$watch('syncRegistrations', function() { + if ($scope.syncRegistrations) { + $scope.instance.config.syncRegistrations = "true"; + } else { + $scope.instance.config.syncRegistrations = "false"; + } + }) + $scope.$watch('instance', function() { if (!angular.equals($scope.instance, instance)) { $scope.changed = true; @@ -510,6 +520,7 @@ module.controller('LDAPCtrl', function($scope, $location, Notifications, Dialog, $scope.instance.providerName = "ldap"; $scope.instance.config = {}; $scope.instance.priority = 0; + $scope.syncRegistrations = false; } $scope.changed = false; $scope.lastVendor = $scope.instance.config.vendor; diff --git a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html index 9916be562e..ffac6b7ab0 100755 --- a/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html +++ b/forms/common-themes/src/main/resources/theme/admin/base/resources/partials/federated-ldap.html @@ -33,6 +33,12 @@ +
+ +
+ +
+
diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java index 14be8cc194..787ca90d44 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationManager.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationManager.java @@ -21,13 +21,7 @@ public class UserFederationManager implements UserProvider { @Override public UserModel addUser(RealmModel realm, String id, String username, boolean addDefaultRoles) { UserModel user = session.userStorage().addUser(realm, id, username, addDefaultRoles); - for (UserFederationProviderModel federation : realm.getUserFederationProviders()) { - UserFederationProvider fed = session.getProvider(UserFederationProvider.class, federation.getProviderName()); - if (fed.isRegistrationSupported()) { - return fed.register(realm, user); - } - } - return user; + return registerWithFederation(realm, user); } protected UserFederationProvider getFederationProvider(UserFederationProviderModel model) { @@ -39,9 +33,14 @@ public class UserFederationManager implements UserProvider { @Override public UserModel addUser(RealmModel realm, String username) { UserModel user = session.userStorage().addUser(realm, username); + return registerWithFederation(realm, user); + } + + protected UserModel registerWithFederation(RealmModel realm, UserModel user) { for (UserFederationProviderModel federation : realm.getUserFederationProviders()) { UserFederationProvider fed = getFederationProvider(federation); - if (fed.isRegistrationSupported()) { + if (fed.synchronizeRegistrations()) { + user.setFederationLink(federation.getId()); return fed.register(realm, user); } } diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java index 2ea322f286..c45812f1e6 100755 --- a/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationProvider.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.Set; /** + * SPI for plugging in federation storage. * * @author Bill Burke * @version $Revision: 1 $ @@ -18,8 +19,25 @@ public interface UserFederationProvider extends Provider { public static final String FIRST_NAME = UserModel.EMAIL; public static final String LAST_NAME = UserModel.LAST_NAME; + /** + * Gives the provider an option to proxy UserModels loaded from local storage. + * This method is called whenever a UserModel is pulled from local storage. + * For example, the LDAP provider proxies the UserModel and does on-demand synchronization with + * LDAP whenever UserModel update methods are invoked. It also overrides UserModel.updateCredential for the + * credential types it supports + * + * @param local + * @return + */ UserModel proxy(UserModel local); - boolean isRegistrationSupported(); + + /** + * Should user registrations be synchronized with this provider? + * FYI, only one provider will be chosen (by priority) to have this synchronization + * + * @return + */ + boolean synchronizeRegistrations(); UserModel register(RealmModel realm, UserModel user); boolean removeUser(RealmModel realm, UserModel user); @@ -54,8 +72,30 @@ public interface UserFederationProvider extends Provider { void preRemove(RealmModel realm); void preRemove(RealmModel realm, RoleModel role); + /** + * Is the Keycloak UserModel still valid and/or existing in federated storage? + * + * @param local + * @return + */ boolean isValid(UserModel local); + + /** + * What UserCredentialModel types are supported by this provider. Keycloak will only call + * validCredentials() with the credential types specified in this method. + * + * @return + */ Set getSupportedCredentialTypes(); + + /** + * Validate credentials for this user. + * + * @param realm + * @param user + * @param input + * @return + */ boolean validCredentials(RealmModel realm, UserModel user, List input); boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input); void close();} diff --git a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java index 5d2c36b738..7ebf592e7e 100755 --- a/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java +++ b/testsuite/integration/src/main/java/org/keycloak/testutils/DummyUserFederationProvider.java @@ -22,7 +22,7 @@ public class DummyUserFederationProvider implements UserFederationProvider { } @Override - public boolean isRegistrationSupported() { + public boolean synchronizeRegistrations() { return false; } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java index 0dbb9474f6..81304c8ced 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/forms/FederationProvidersIntegrationTest.java @@ -10,7 +10,9 @@ import org.junit.rules.RuleChain; import org.junit.rules.TestRule; import org.junit.runners.MethodSorters; import org.keycloak.OAuth2Constants; +import org.keycloak.federation.ldap.LDAPFederationProvider; import org.keycloak.federation.ldap.LDAPFederationProviderFactory; +import org.keycloak.models.UserFederationProviderModel; import org.keycloak.testutils.LDAPEmbeddedServer; import org.keycloak.testsuite.LDAPTestUtils; import org.keycloak.models.KeycloakSession; @@ -45,6 +47,8 @@ public class FederationProvidersIntegrationTest { private static Map ldapConfig = null; + private static UserFederationProviderModel ldapModel = null; + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { @Override @@ -61,10 +65,11 @@ public class FederationProvidersIntegrationTest { ldapConfig.put(LDAPConstants.USER_DN_SUFFIX, ldapServer.getUserDnSuffix()); String vendor = ldapServer.getVendor(); ldapConfig.put(LDAPConstants.VENDOR, vendor); + ldapConfig.put(LDAPFederationProvider.SYNC_REGISTRATIONS, "true"); - appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, null); + ldapModel = appRealm.addUserFederationProvider(LDAPFederationProviderFactory.PROVIDER_NAME, ldapConfig, 0, null); // Configure LDAP ldapRule.getEmbeddedServer().setupLdapInRealm(appRealm); @@ -187,5 +192,13 @@ public class FederationProvidersIntegrationTest { registerPage.register("firstName", "lastName", "email2", "registerUserSuccess2", "password", "password"); Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + + KeycloakSession session = keycloakRule.startSession(); + RealmModel appRealm = session.realms().getRealmByName("test"); + UserModel user = session.users().getUserByUsername("registerUserSuccess2", appRealm); + Assert.assertNotNull(user); + Assert.assertNotNull(user.getFederationLink()); + Assert.assertEquals(user.getFederationLink(), ldapModel.getId()); + keycloakRule.stopSession(session, false); } }