support registration ldap

This commit is contained in:
Bill Burke 2014-07-31 15:22:20 -04:00
parent 705da88daa
commit a084695978
8 changed files with 88 additions and 15 deletions

View file

@ -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 <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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);

View file

@ -11,6 +11,7 @@ import java.util.Collections;
import java.util.List;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/

View file

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

View file

@ -33,6 +33,12 @@
<input class="form-control" id="priority" type="text" ng-model="instance.priority">
</div>
</div>
<div class="form-group clearfix block">
<label class="col-sm-2 control-label" for="syncRegistrations">Sync Registrations</label>
<div class="col-sm-4">
<input ng-model="syncRegistrations" name="syncRegistrations" id="syncRegistrations" onoffswitch />
</div>
</div>
<div class="form-group clearfix">
<label class="col-sm-2 control-label" for="vendor">Vendor</label>
<div class="col-sm-4">

View file

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

View file

@ -7,6 +7,7 @@ import java.util.Map;
import java.util.Set;
/**
* SPI for plugging in federation storage.
*
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @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<String> getSupportedCredentialTypes();
/**
* Validate credentials for this user.
*
* @param realm
* @param user
* @param input
* @return
*/
boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input);
boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input);
void close();}

View file

@ -22,7 +22,7 @@ public class DummyUserFederationProvider implements UserFederationProvider {
}
@Override
public boolean isRegistrationSupported() {
public boolean synchronizeRegistrations() {
return false;
}

View file

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