KEYCLOAK-1385 Introduce end-of-line normalization

This commit is contained in:
Stian Thorgersen committed 2015-07-17 13:46:51 +02:00
1 parent be8aa93cca
commit 1642ac2394
888 files changed
+110680 -110660

No files matched your search

@@ -1,452 +1,452 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
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);
protected LDAPFederationProviderFactory factory;
protected KeycloakSession session;
protected UserFederationProviderModel model;
protected LDAPIdentityStore ldapIdentityStore;
protected EditMode editMode;
protected LDAPProviderKerberosConfig kerberosConfig;
protected final Set<String> supportedCredentialTypes = new HashSet<>();
public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) {
this.factory = factory;
this.session = session;
this.model = model;
this.ldapIdentityStore = ldapIdentityStore;
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
this.editMode = ldapIdentityStore.getConfig().getEditMode();
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
if (kerberosConfig.isAllowKerberosAuthentication()) {
supportedCredentialTypes.add(UserCredentialModel.KERBEROS);
}
}
public KeycloakSession getSession() {
return session;
}
public UserFederationProviderModel getModel() {
return model;
}
public LDAPIdentityStore getLdapIdentityStore() {
return this.ldapIdentityStore;
}
public EditMode getEditMode() {
return editMode;
}
@Override
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
LDAPObject ldapObject = loadAndValidateUser(realm, local);
if (ldapObject == null) {
return null;
}
return proxy(realm, local, ldapObject);
}
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
UserModel proxied = local;
switch (editMode) {
case READ_ONLY:
proxied = new ReadonlyLDAPUserModelDelegate(local, this);
break;
case WRITABLE:
proxied = new WritableLDAPUserModelDelegate(local, this, ldapObject);
break;
case UNSYNCED:
proxied = new UnsyncedLDAPUserModelDelegate(local, this);
}
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
}
return proxied;
}
@Override
public Set<String> getSupportedCredentialTypes(UserModel local) {
Set<String> supportedCredentialTypes = new HashSet<String>(this.supportedCredentialTypes);
if (editMode == EditMode.UNSYNCED ) {
for (UserCredentialValueModel cred : local.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
// User has changed password in KC local database. Use KC password instead of LDAP password
supportedCredentialTypes.remove(UserCredentialModel.PASSWORD);
}
}
}
return supportedCredentialTypes;
}
@Override
public Set<String> getSupportedCredentialTypes() {
return new HashSet<String>(this.supportedCredentialTypes);
}
@Override
public boolean synchronizeRegistrations() {
return "true".equalsIgnoreCase(model.getConfig().get(LDAPConstants.SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
}
@Override
public UserModel register(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
user.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
return proxy(realm, user, ldapObject);
}
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'", user.getUsername(), editMode.toString());
return false;
}
LDAPObject ldapObject = loadAndValidateUser(realm, user);
if (ldapObject == null) {
logger.warnf("User '%s' can't be deleted from LDAP as it doesn't exist here", user.getUsername());
return false;
}
ldapIdentityStore.remove(ldapObject);
return true;
}
@Override
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
List<UserModel> searchResults =new LinkedList<UserModel>();
List<LDAPObject> ldapUsers = searchLDAP(realm, attributes, maxResults);
for (LDAPObject ldapUser : ldapUsers) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
UserModel imported = importUserFromLDAP(session, realm, ldapUser);
searchResults.add(imported);
}
}
return searchResults;
}
protected List<LDAPObject> searchLDAP(RealmModel realm, Map<String, String> attributes, int maxResults) {
List<LDAPObject> results = new ArrayList<LDAPObject>();
if (attributes.containsKey(USERNAME)) {
LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(USERNAME));
if (user != null) {
results.add(user);
}
}
if (attributes.containsKey(EMAIL)) {
LDAPObject user = queryByEmail(realm, attributes.get(EMAIL));
if (user != null) {
results.add(user);
}
}
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
// Mapper should replace parameter with correct LDAP mapped attributes
if (attributes.containsKey(FIRST_NAME)) {
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(FIRST_NAME), attributes.get(FIRST_NAME)));
}
if (attributes.containsKey(LAST_NAME)) {
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(LAST_NAME), attributes.get(LAST_NAME)));
}
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
results.addAll(ldapObjects);
}
return results;
}
/**
* @param local
* @return ldapUser corresponding to local user or null if user is no longer in LDAP
*/
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
if (ldapUser == null) {
return null;
}
if (ldapUser.getUuid().equals(local.getFirstAttribute(LDAPConstants.LDAP_ID))) {
return ldapUser;
} else {
logger.warnf("LDAP User invalid. ID doesn't match. ID from LDAP [%s], LDAP ID from local DB: [%s]", ldapUser.getUuid(), local.getFirstAttribute(LDAPConstants.LDAP_ID));
return null;
}
}
@Override
public boolean isValid(RealmModel realm, UserModel local) {
return loadAndValidateUser(realm, local) != null;
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, username);
if (ldapUser == null) {
return null;
}
return importUserFromLDAP(session, realm, ldapUser);
}
protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (ldapUsername == null) {
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " +
ldapIdentityStore.getConfig().getUsernameLdapAttribute() + ", attributes from LDAP: " + ldapUser.getAttributes());
}
UserModel imported = session.userStorage().addUser(realm, ldapUsername);
imported.setEnabled(true);
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
}
String userDN = ldapUser.getDn().toString();
imported.setFederationLink(model.getId());
imported.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
imported.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
ldapUser.getUuid(), userDN);
return proxy(realm, imported, ldapUser);
}
protected LDAPObject queryByEmail(RealmModel realm, String email) {
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
Condition emailCondition = conditionsBuilder.equal(new QueryParameter(UserModel.EMAIL), email);
ldapQuery.where(emailCondition);
return ldapQuery.getFirstResult();
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
LDAPObject ldapUser = queryByEmail(realm, email);
if (ldapUser == null) {
return null;
}
return importUserFromLDAP(session, realm, ldapUser);
}
@Override
public void preRemove(RealmModel realm) {
// complete Don't think we have to do anything
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// TODO: Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper?
}
public boolean validPassword(RealmModel realm, UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
return authenticator.validUser(user.getUsername(), password);
} else {
// Use Naming LDAP API
LDAPObject ldapUser = loadAndValidateUser(realm, user);
return ldapIdentityStore.validatePassword(ldapUser, password);
}
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
return validPassword(realm, user, cred.getValue());
} else {
return false; // invalid cred type
}
}
return true;
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(realm, user, Arrays.asList(input));
}
@Override
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {
if (kerberosConfig.isAllowKerberosAuthentication()) {
String spnegoToken = credential.getValue();
SPNEGOAuthenticator spnegoAuthenticator = factory.createSPNEGOAuthenticator(spnegoToken, kerberosConfig);
spnegoAuthenticator.authenticate();
Map<String, String> state = new HashMap<String, String>();
if (spnegoAuthenticator.isAuthenticated()) {
// TODO: This assumes that LDAP "uid" is equal to kerberos principal name. Like uid "hnelson" and kerberos principal "hnelson@KEYCLOAK.ORG".
// Check if it's correct or if LDAP attribute for mapping kerberos principal should be available (For ApacheDS it seems to be attribute "krb5PrincipalName" but on MSAD it's likely different)
String username = spnegoAuthenticator.getAuthenticatedUsername();
UserModel user = findOrCreateAuthenticatedUser(realm, username);
if (user == null) {
logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getDisplayName());
return CredentialValidationOutput.failed();
} else {
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
if (delegationCredential != null) {
state.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, delegationCredential);
}
return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
}
} else {
state.put(KerberosConstants.RESPONSE_TOKEN, spnegoAuthenticator.getResponseToken());
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.CONTINUE, state);
}
}
}
return CredentialValidationOutput.failed();
}
@Override
public void close() {
}
/**
* Called after successful kerberos authentication
*
* @param realm realm
* @param username username without realm prefix
* @return finded or newly created user
*/
protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) {
UserModel user = session.userStorage().getUserByUsername(username, realm);
if (user != null) {
logger.debugf("Kerberos authenticated user [%s] found in Keycloak storage", username);
if (!model.getId().equals(user.getFederationLink())) {
logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
return null;
} else {
LDAPObject ldapObject = loadAndValidateUser(realm, user);
if (ldapObject != null) {
return proxy(realm, user, ldapObject);
} else {
logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
username, model.getDisplayName(), user.getFirstAttribute(LDAPConstants.LDAP_ID));
logger.warn("Will re-create user");
session.userStorage().removeUser(realm, user);
}
}
}
// Creating user to local storage
logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
return getUserByUsername(realm, username);
}
public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
Condition usernameCondition = conditionsBuilder.equal(new QueryParameter(usernameMappedAttribute), username);
ldapQuery.where(usernameCondition);
LDAPObject ldapUser = ldapQuery.getFirstResult();
if (ldapUser == null) {
return null;
}
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (!username.equals(ldapUsername)) {
logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUsername);
return null;
}
return ldapUser;
}
public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType());
if (ldapMapper == null) {
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperType());
}
return ldapMapper;
}
}
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.kerberos.LDAPProviderKerberosConfig;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.models.CredentialValidationOutput;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.RoleModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserCredentialValueModel;
import org.keycloak.mappers.UserFederationMapper;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.constants.KerberosConstants;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
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);
protected LDAPFederationProviderFactory factory;
protected KeycloakSession session;
protected UserFederationProviderModel model;
protected LDAPIdentityStore ldapIdentityStore;
protected EditMode editMode;
protected LDAPProviderKerberosConfig kerberosConfig;
protected final Set<String> supportedCredentialTypes = new HashSet<>();
public LDAPFederationProvider(LDAPFederationProviderFactory factory, KeycloakSession session, UserFederationProviderModel model, LDAPIdentityStore ldapIdentityStore) {
this.factory = factory;
this.session = session;
this.model = model;
this.ldapIdentityStore = ldapIdentityStore;
this.kerberosConfig = new LDAPProviderKerberosConfig(model);
this.editMode = ldapIdentityStore.getConfig().getEditMode();
supportedCredentialTypes.add(UserCredentialModel.PASSWORD);
if (kerberosConfig.isAllowKerberosAuthentication()) {
supportedCredentialTypes.add(UserCredentialModel.KERBEROS);
}
}
public KeycloakSession getSession() {
return session;
}
public UserFederationProviderModel getModel() {
return model;
}
public LDAPIdentityStore getLdapIdentityStore() {
return this.ldapIdentityStore;
}
public EditMode getEditMode() {
return editMode;
}
@Override
public UserModel validateAndProxy(RealmModel realm, UserModel local) {
LDAPObject ldapObject = loadAndValidateUser(realm, local);
if (ldapObject == null) {
return null;
}
return proxy(realm, local, ldapObject);
}
protected UserModel proxy(RealmModel realm, UserModel local, LDAPObject ldapObject) {
UserModel proxied = local;
switch (editMode) {
case READ_ONLY:
proxied = new ReadonlyLDAPUserModelDelegate(local, this);
break;
case WRITABLE:
proxied = new WritableLDAPUserModelDelegate(local, this, ldapObject);
break;
case UNSYNCED:
proxied = new UnsyncedLDAPUserModelDelegate(local, this);
}
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(model.getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm);
}
return proxied;
}
@Override
public Set<String> getSupportedCredentialTypes(UserModel local) {
Set<String> supportedCredentialTypes = new HashSet<String>(this.supportedCredentialTypes);
if (editMode == EditMode.UNSYNCED ) {
for (UserCredentialValueModel cred : local.getCredentialsDirectly()) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
// User has changed password in KC local database. Use KC password instead of LDAP password
supportedCredentialTypes.remove(UserCredentialModel.PASSWORD);
}
}
}
return supportedCredentialTypes;
}
@Override
public Set<String> getSupportedCredentialTypes() {
return new HashSet<String>(this.supportedCredentialTypes);
}
@Override
public boolean synchronizeRegistrations() {
return "true".equalsIgnoreCase(model.getConfig().get(LDAPConstants.SYNC_REGISTRATIONS)) && editMode == EditMode.WRITABLE;
}
@Override
public UserModel register(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) throw new IllegalStateException("Registration is not supported by this ldap server");
if (!synchronizeRegistrations()) throw new IllegalStateException("Registration is not supported by this ldap server");
LDAPObject ldapObject = LDAPUtils.addUserToLDAP(this, realm, user);
user.setSingleAttribute(LDAPConstants.LDAP_ID, ldapObject.getUuid());
user.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, ldapObject.getDn().toString());
return proxy(realm, user, ldapObject);
}
@Override
public boolean removeUser(RealmModel realm, UserModel user) {
if (editMode == EditMode.READ_ONLY || editMode == EditMode.UNSYNCED) {
logger.warnf("User '%s' can't be deleted in LDAP as editMode is '%s'", user.getUsername(), editMode.toString());
return false;
}
LDAPObject ldapObject = loadAndValidateUser(realm, user);
if (ldapObject == null) {
logger.warnf("User '%s' can't be deleted from LDAP as it doesn't exist here", user.getUsername());
return false;
}
ldapIdentityStore.remove(ldapObject);
return true;
}
@Override
public List<UserModel> searchByAttributes(Map<String, String> attributes, RealmModel realm, int maxResults) {
List<UserModel> searchResults =new LinkedList<UserModel>();
List<LDAPObject> ldapUsers = searchLDAP(realm, attributes, maxResults);
for (LDAPObject ldapUser : ldapUsers) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, this.ldapIdentityStore.getConfig());
if (session.userStorage().getUserByUsername(ldapUsername, realm) == null) {
UserModel imported = importUserFromLDAP(session, realm, ldapUser);
searchResults.add(imported);
}
}
return searchResults;
}
protected List<LDAPObject> searchLDAP(RealmModel realm, Map<String, String> attributes, int maxResults) {
List<LDAPObject> results = new ArrayList<LDAPObject>();
if (attributes.containsKey(USERNAME)) {
LDAPObject user = loadLDAPUserByUsername(realm, attributes.get(USERNAME));
if (user != null) {
results.add(user);
}
}
if (attributes.containsKey(EMAIL)) {
LDAPObject user = queryByEmail(realm, attributes.get(EMAIL));
if (user != null) {
results.add(user);
}
}
if (attributes.containsKey(FIRST_NAME) || attributes.containsKey(LAST_NAME)) {
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
// Mapper should replace parameter with correct LDAP mapped attributes
if (attributes.containsKey(FIRST_NAME)) {
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(FIRST_NAME), attributes.get(FIRST_NAME)));
}
if (attributes.containsKey(LAST_NAME)) {
ldapQuery.where(conditionsBuilder.equal(new QueryParameter(LAST_NAME), attributes.get(LAST_NAME)));
}
List<LDAPObject> ldapObjects = ldapQuery.getResultList();
results.addAll(ldapObjects);
}
return results;
}
/**
* @param local
* @return ldapUser corresponding to local user or null if user is no longer in LDAP
*/
protected LDAPObject loadAndValidateUser(RealmModel realm, UserModel local) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, local.getUsername());
if (ldapUser == null) {
return null;
}
if (ldapUser.getUuid().equals(local.getFirstAttribute(LDAPConstants.LDAP_ID))) {
return ldapUser;
} else {
logger.warnf("LDAP User invalid. ID doesn't match. ID from LDAP [%s], LDAP ID from local DB: [%s]", ldapUser.getUuid(), local.getFirstAttribute(LDAPConstants.LDAP_ID));
return null;
}
}
@Override
public boolean isValid(RealmModel realm, UserModel local) {
return loadAndValidateUser(realm, local) != null;
}
@Override
public UserModel getUserByUsername(RealmModel realm, String username) {
LDAPObject ldapUser = loadLDAPUserByUsername(realm, username);
if (ldapUser == null) {
return null;
}
return importUserFromLDAP(session, realm, ldapUser);
}
protected UserModel importUserFromLDAP(KeycloakSession session, RealmModel realm, LDAPObject ldapUser) {
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (ldapUsername == null) {
throw new ModelException("User returned from LDAP has null username! Check configuration of your LDAP mappings. Mapped username LDAP attribute: " +
ldapIdentityStore.getConfig().getUsernameLdapAttribute() + ", attributes from LDAP: " + ldapUser.getAttributes());
}
UserModel imported = session.userStorage().addUser(realm, ldapUsername);
imported.setEnabled(true);
Set<UserFederationMapperModel> federationMappers = realm.getUserFederationMappersByFederationProvider(getModel().getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true);
}
String userDN = ldapUser.getDn().toString();
imported.setFederationLink(model.getId());
imported.setSingleAttribute(LDAPConstants.LDAP_ID, ldapUser.getUuid());
imported.setSingleAttribute(LDAPConstants.LDAP_ENTRY_DN, userDN);
logger.debugf("Imported new user from LDAP to Keycloak DB. Username: [%s], Email: [%s], LDAP_ID: [%s], LDAP Entry DN: [%s]", imported.getUsername(), imported.getEmail(),
ldapUser.getUuid(), userDN);
return proxy(realm, imported, ldapUser);
}
protected LDAPObject queryByEmail(RealmModel realm, String email) {
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
// Mapper should replace "email" in parameter name with correct LDAP mapped attribute
Condition emailCondition = conditionsBuilder.equal(new QueryParameter(UserModel.EMAIL), email);
ldapQuery.where(emailCondition);
return ldapQuery.getFirstResult();
}
@Override
public UserModel getUserByEmail(RealmModel realm, String email) {
LDAPObject ldapUser = queryByEmail(realm, email);
if (ldapUser == null) {
return null;
}
return importUserFromLDAP(session, realm, ldapUser);
}
@Override
public void preRemove(RealmModel realm) {
// complete Don't think we have to do anything
}
@Override
public void preRemove(RealmModel realm, RoleModel role) {
// TODO: Maybe mappers callback to ensure role deletion propagated to LDAP by RoleLDAPFederationMapper?
}
public boolean validPassword(RealmModel realm, UserModel user, String password) {
if (kerberosConfig.isAllowKerberosAuthentication() && kerberosConfig.isUseKerberosForPasswordAuthentication()) {
// Use Kerberos JAAS (Krb5LoginModule)
KerberosUsernamePasswordAuthenticator authenticator = factory.createKerberosUsernamePasswordAuthenticator(kerberosConfig);
return authenticator.validUser(user.getUsername(), password);
} else {
// Use Naming LDAP API
LDAPObject ldapUser = loadAndValidateUser(realm, user);
return ldapIdentityStore.validatePassword(ldapUser, password);
}
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, List<UserCredentialModel> input) {
for (UserCredentialModel cred : input) {
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
return validPassword(realm, user, cred.getValue());
} else {
return false; // invalid cred type
}
}
return true;
}
@Override
public boolean validCredentials(RealmModel realm, UserModel user, UserCredentialModel... input) {
return validCredentials(realm, user, Arrays.asList(input));
}
@Override
public CredentialValidationOutput validCredentials(RealmModel realm, UserCredentialModel credential) {
if (credential.getType().equals(UserCredentialModel.KERBEROS)) {
if (kerberosConfig.isAllowKerberosAuthentication()) {
String spnegoToken = credential.getValue();
SPNEGOAuthenticator spnegoAuthenticator = factory.createSPNEGOAuthenticator(spnegoToken, kerberosConfig);
spnegoAuthenticator.authenticate();
Map<String, String> state = new HashMap<String, String>();
if (spnegoAuthenticator.isAuthenticated()) {
// TODO: This assumes that LDAP "uid" is equal to kerberos principal name. Like uid "hnelson" and kerberos principal "hnelson@KEYCLOAK.ORG".
// Check if it's correct or if LDAP attribute for mapping kerberos principal should be available (For ApacheDS it seems to be attribute "krb5PrincipalName" but on MSAD it's likely different)
String username = spnegoAuthenticator.getAuthenticatedUsername();
UserModel user = findOrCreateAuthenticatedUser(realm, username);
if (user == null) {
logger.warnf("Kerberos/SPNEGO authentication succeeded with username [%s], but couldn't find or create user with federation provider [%s]", username, model.getDisplayName());
return CredentialValidationOutput.failed();
} else {
String delegationCredential = spnegoAuthenticator.getSerializedDelegationCredential();
if (delegationCredential != null) {
state.put(KerberosConstants.GSS_DELEGATION_CREDENTIAL, delegationCredential);
}
return new CredentialValidationOutput(user, CredentialValidationOutput.Status.AUTHENTICATED, state);
}
} else {
state.put(KerberosConstants.RESPONSE_TOKEN, spnegoAuthenticator.getResponseToken());
return new CredentialValidationOutput(null, CredentialValidationOutput.Status.CONTINUE, state);
}
}
}
return CredentialValidationOutput.failed();
}
@Override
public void close() {
}
/**
* Called after successful kerberos authentication
*
* @param realm realm
* @param username username without realm prefix
* @return finded or newly created user
*/
protected UserModel findOrCreateAuthenticatedUser(RealmModel realm, String username) {
UserModel user = session.userStorage().getUserByUsername(username, realm);
if (user != null) {
logger.debugf("Kerberos authenticated user [%s] found in Keycloak storage", username);
if (!model.getId().equals(user.getFederationLink())) {
logger.warnf("User with username [%s] already exists, but is not linked to provider [%s]", username, model.getDisplayName());
return null;
} else {
LDAPObject ldapObject = loadAndValidateUser(realm, user);
if (ldapObject != null) {
return proxy(realm, user, ldapObject);
} else {
logger.warnf("User with username [%s] aready exists and is linked to provider [%s] but is not valid. Stale LDAP_ID on local user is: %s",
username, model.getDisplayName(), user.getFirstAttribute(LDAPConstants.LDAP_ID));
logger.warn("Will re-create user");
session.userStorage().removeUser(realm, user);
}
}
}
// Creating user to local storage
logger.debugf("Kerberos authenticated user [%s] not in Keycloak storage. Creating him", username);
return getUserByUsername(realm, username);
}
public LDAPObject loadLDAPUserByUsername(RealmModel realm, String username) {
LDAPQuery ldapQuery = LDAPUtils.createQueryForUserSearch(this, realm);
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
String usernameMappedAttribute = this.ldapIdentityStore.getConfig().getUsernameLdapAttribute();
Condition usernameCondition = conditionsBuilder.equal(new QueryParameter(usernameMappedAttribute), username);
ldapQuery.where(usernameCondition);
LDAPObject ldapUser = ldapQuery.getFirstResult();
if (ldapUser == null) {
return null;
}
// KEYCLOAK-808: Should we allow case-sensitivity to be configurable?
String ldapUsername = LDAPUtils.getUsername(ldapUser, ldapIdentityStore.getConfig());
if (!username.equals(ldapUsername)) {
logger.warnf("User found in LDAP but with different username. LDAP username: %s, Searched username: %s", username, ldapUsername);
return null;
}
return ldapUser;
}
public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) {
LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType());
if (ldapMapper == null) {
throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperType());
}
return ldapMapper;
}
}
@@ -1,362 +1,362 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationEventAwareProviderFactory;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collections;
import java.util.Date;
import java.util.List;
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 LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory {
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
private LDAPIdentityStoreRegistry ldapStoreRegistry;
@Override
public UserFederationProvider create(KeycloakSession session) {
throw new IllegalAccessError("Illegal to call this method");
}
@Override
public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
return new LDAPFederationProvider(this, session, model, ldapIdentityStore);
}
@Override
public void init(Config.Scope config) {
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
}
@Override
public void close() {
this.ldapStoreRegistry = null;
}
@Override
public String getId() {
return PROVIDER_NAME;
}
@Override
public Set<String> getConfigurationOptions() {
return Collections.emptySet();
}
// Best effort to create appropriate mappers according to our LDAP config
@Override
public void onProviderModelCreated(RealmModel realm, UserFederationProviderModel newProviderModel) {
LDAPConfig ldapConfig = new LDAPConfig(newProviderModel.getConfig());
boolean activeDirectory = ldapConfig.isActiveDirectory();
UserFederationProvider.EditMode editMode = ldapConfig.getEditMode();
String readOnly = String.valueOf(editMode == UserFederationProvider.EditMode.READ_ONLY || editMode == UserFederationProvider.EditMode.UNSYNCED);
String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();
String alwaysReadValueFromLDAP = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.WRITABLE);
UserFederationMapperModel mapperModel;
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
// CN is typically used as RDN for Active Directory deployments
if (ldapConfig.getRdnLdapAttribute().equalsIgnoreCase(LDAPConstants.CN)) {
if (usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.CN)) {
// For AD deployments with "cn" as username, we will map "givenName" to first name
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
} else {
if (editMode == UserFederationProvider.EditMode.WRITABLE) {
// For AD deployments with "sAMAccountName" as username and writable, we need to map "cn" as username as well (this is needed so we can register new users from KC into LDAP) and we will map "givenName" to first name.
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
} else {
// For read-only LDAP, we map "cn" as full name
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel);
}
}
} else {
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
}
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel);
String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
// map createTimeStamp as read-only
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel);
// map modifyTimeStamp as read-only
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel);
}
@Override
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model);
// TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
logger.infof("Sync all users finished: %s", syncResult.getStatus());
return syncResult;
}
@Override
public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getDisplayName());
// Sync newly created and updated users
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.CREATE_TIMESTAMP), lastSync);
Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
userQuery.where(orCondition);
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
logger.infof("Sync changed users finished: %s", result.getStatus());
return result;
}
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
if (pagination) {
String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
boolean nextPage = true;
while (nextPage) {
userQuery.setLimit(pageSize);
final List<LDAPObject> users = userQuery.getResultList();
nextPage = userQuery.getPaginationContext() != null;
UserFederationSyncResult currentPageSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
syncResult.add(currentPageSync);
}
} else {
// LDAP pagination not available. Do everything in single transaction
final List<LDAPObject> users = userQuery.getResultList();
UserFederationSyncResult currentSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
syncResult.add(currentSync);
}
return syncResult;
}
private LDAPQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
class QueryHolder {
LDAPQuery query;
}
final QueryHolder queryHolder = new QueryHolder();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, model);
RealmModel realm = session.realms().getRealm(realmId);
queryHolder.query = LDAPUtils.createQueryForUserSearch(ldapFedProvider, realm);
}
});
return queryHolder.query;
}
protected UserFederationSyncResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
class BooleanHolder {
private boolean value = true;
}
final BooleanHolder exists = new BooleanHolder();
for (final LDAPObject ldapUser : ldapUsers) {
try {
// Process each user in it's own transaction to avoid global fail
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
RealmModel currentRealm = session.realms().getRealm(realmId);
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
UserModel currentUser = session.userStorage().getUserByUsername(username, currentRealm);
if (currentUser == null) {
// Add new user to Keycloak
exists.value = false;
ldapFedProvider.importUserFromLDAP(session, currentRealm, ldapUser);
syncResult.increaseAdded();
} else {
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
// Update keycloak user
Set<UserFederationMapperModel> federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false);
}
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
syncResult.increaseUpdated();
} else {
logger.warnf("User '%s' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider '%s'", username, fedModel.getDisplayName());
syncResult.increaseFailed();
}
}
}
});
} catch (ModelException me) {
logger.error("Failed during import user from LDAP", me);
syncResult.increaseFailed();
// Remove user if we already added him during this transaction
if (!exists.value) {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
RealmModel currentRealm = session.realms().getRealm(realmId);
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
UserModel existing = session.userStorage().getUserByUsername(username, currentRealm);
if (existing != null) {
session.userStorage().removeUser(currentRealm, existing);
}
}
});
}
}
}
return syncResult;
}
protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
KerberosServerSubjectAuthenticator kerberosAuth = createKerberosSubjectAuthenticator(kerberosConfig);
return new SPNEGOAuthenticator(kerberosConfig, kerberosAuth, spnegoToken);
}
protected KerberosServerSubjectAuthenticator createKerberosSubjectAuthenticator(CommonKerberosConfig kerberosConfig) {
return new KerberosServerSubjectAuthenticator(kerberosConfig);
}
protected KerberosUsernamePasswordAuthenticator createKerberosUsernamePasswordAuthenticator(CommonKerberosConfig kerberosConfig) {
return new KerberosUsernamePasswordAuthenticator(kerberosConfig);
}
}
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.Config;
import org.keycloak.federation.kerberos.CommonKerberosConfig;
import org.keycloak.federation.kerberos.impl.KerberosServerSubjectAuthenticator;
import org.keycloak.federation.kerberos.impl.KerberosUsernamePasswordAuthenticator;
import org.keycloak.federation.kerberos.impl.SPNEGOAuthenticator;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.query.Condition;
import org.keycloak.federation.ldap.idm.query.QueryParameter;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQuery;
import org.keycloak.federation.ldap.idm.query.internal.LDAPQueryConditionsBuilder;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory;
import org.keycloak.federation.ldap.mappers.LDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapper;
import org.keycloak.federation.ldap.mappers.UserAttributeLDAPFederationMapperFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.models.KeycloakSessionTask;
import org.keycloak.models.LDAPConstants;
import org.keycloak.models.ModelDuplicateException;
import org.keycloak.models.ModelException;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserFederationEventAwareProviderFactory;
import org.keycloak.models.UserFederationMapperModel;
import org.keycloak.models.UserFederationProvider;
import org.keycloak.models.UserFederationProviderModel;
import org.keycloak.models.UserFederationSyncResult;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.KeycloakModelUtils;
import java.util.Collections;
import java.util.Date;
import java.util.List;
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 LDAPFederationProviderFactory extends UserFederationEventAwareProviderFactory {
private static final Logger logger = Logger.getLogger(LDAPFederationProviderFactory.class);
public static final String PROVIDER_NAME = LDAPConstants.LDAP_PROVIDER;
private LDAPIdentityStoreRegistry ldapStoreRegistry;
@Override
public UserFederationProvider create(KeycloakSession session) {
throw new IllegalAccessError("Illegal to call this method");
}
@Override
public LDAPFederationProvider getInstance(KeycloakSession session, UserFederationProviderModel model) {
LDAPIdentityStore ldapIdentityStore = this.ldapStoreRegistry.getLdapStore(model);
return new LDAPFederationProvider(this, session, model, ldapIdentityStore);
}
@Override
public void init(Config.Scope config) {
this.ldapStoreRegistry = new LDAPIdentityStoreRegistry();
}
@Override
public void close() {
this.ldapStoreRegistry = null;
}
@Override
public String getId() {
return PROVIDER_NAME;
}
@Override
public Set<String> getConfigurationOptions() {
return Collections.emptySet();
}
// Best effort to create appropriate mappers according to our LDAP config
@Override
public void onProviderModelCreated(RealmModel realm, UserFederationProviderModel newProviderModel) {
LDAPConfig ldapConfig = new LDAPConfig(newProviderModel.getConfig());
boolean activeDirectory = ldapConfig.isActiveDirectory();
UserFederationProvider.EditMode editMode = ldapConfig.getEditMode();
String readOnly = String.valueOf(editMode == UserFederationProvider.EditMode.READ_ONLY || editMode == UserFederationProvider.EditMode.UNSYNCED);
String usernameLdapAttribute = ldapConfig.getUsernameLdapAttribute();
String alwaysReadValueFromLDAP = String.valueOf(editMode==UserFederationProvider.EditMode.READ_ONLY || editMode== UserFederationProvider.EditMode.WRITABLE);
UserFederationMapperModel mapperModel;
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, usernameLdapAttribute,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
// CN is typically used as RDN for Active Directory deployments
if (ldapConfig.getRdnLdapAttribute().equalsIgnoreCase(LDAPConstants.CN)) {
if (usernameLdapAttribute.equalsIgnoreCase(LDAPConstants.CN)) {
// For AD deployments with "cn" as username, we will map "givenName" to first name
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
} else {
if (editMode == UserFederationProvider.EditMode.WRITABLE) {
// For AD deployments with "sAMAccountName" as username and writable, we need to map "cn" as username as well (this is needed so we can register new users from KC into LDAP) and we will map "givenName" to first name.
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.GIVENNAME,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("username-cn", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.USERNAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
} else {
// For read-only LDAP, we map "cn" as full name
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("full name", newProviderModel.getId(), FullNameLDAPFederationMapperFactory.PROVIDER_ID,
FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly);
realm.addUserFederationMapper(mapperModel);
}
}
} else {
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("first name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.FIRST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.CN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
}
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("last name", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.LAST_NAME,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.SN,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "true");
realm.addUserFederationMapper(mapperModel);
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("email", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, UserModel.EMAIL,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, LDAPConstants.EMAIL,
UserAttributeLDAPFederationMapper.READ_ONLY, readOnly,
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, "false",
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel);
String createTimestampLdapAttrName = activeDirectory ? "whenCreated" : LDAPConstants.CREATE_TIMESTAMP;
String modifyTimestampLdapAttrName = activeDirectory ? "whenChanged" : LDAPConstants.MODIFY_TIMESTAMP;
// map createTimeStamp as read-only
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("creation date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.CREATE_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, createTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel);
// map modifyTimeStamp as read-only
mapperModel = KeycloakModelUtils.createUserFederationMapperModel("modify date", newProviderModel.getId(), UserAttributeLDAPFederationMapperFactory.PROVIDER_ID,
UserAttributeLDAPFederationMapper.USER_MODEL_ATTRIBUTE, LDAPConstants.MODIFY_TIMESTAMP,
UserAttributeLDAPFederationMapper.LDAP_ATTRIBUTE, modifyTimestampLdapAttrName,
UserAttributeLDAPFederationMapper.READ_ONLY, "true",
UserAttributeLDAPFederationMapper.ALWAYS_READ_VALUE_FROM_LDAP, alwaysReadValueFromLDAP,
UserAttributeLDAPFederationMapper.IS_MANDATORY_IN_LDAP, "false");
realm.addUserFederationMapper(mapperModel);
}
@Override
public UserFederationSyncResult syncAllUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
logger.infof("Sync all users from LDAP to local store: realm: %s, federation provider: %s", realmId, model.getDisplayName());
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
UserFederationSyncResult syncResult = syncImpl(sessionFactory, userQuery, realmId, model);
// TODO: Remove all existing keycloak users, which have federation links, but are not in LDAP. Perhaps don't check users, which were just added or updated during this sync?
logger.infof("Sync all users finished: %s", syncResult.getStatus());
return syncResult;
}
@Override
public UserFederationSyncResult syncChangedUsers(KeycloakSessionFactory sessionFactory, String realmId, UserFederationProviderModel model, Date lastSync) {
logger.infof("Sync changed users from LDAP to local store: realm: %s, federation provider: %s, last sync time: " + lastSync, realmId, model.getDisplayName());
// Sync newly created and updated users
LDAPQueryConditionsBuilder conditionsBuilder = new LDAPQueryConditionsBuilder();
Condition createCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.CREATE_TIMESTAMP), lastSync);
Condition modifyCondition = conditionsBuilder.greaterThanOrEqualTo(new QueryParameter(LDAPConstants.MODIFY_TIMESTAMP), lastSync);
Condition orCondition = conditionsBuilder.orCondition(createCondition, modifyCondition);
LDAPQuery userQuery = createQuery(sessionFactory, realmId, model);
userQuery.where(orCondition);
UserFederationSyncResult result = syncImpl(sessionFactory, userQuery, realmId, model);
logger.infof("Sync changed users finished: %s", result.getStatus());
return result;
}
protected UserFederationSyncResult syncImpl(KeycloakSessionFactory sessionFactory, LDAPQuery userQuery, final String realmId, final UserFederationProviderModel fedModel) {
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
boolean pagination = Boolean.parseBoolean(fedModel.getConfig().get(LDAPConstants.PAGINATION));
if (pagination) {
String pageSizeConfig = fedModel.getConfig().get(LDAPConstants.BATCH_SIZE_FOR_SYNC);
int pageSize = pageSizeConfig!=null ? Integer.parseInt(pageSizeConfig) : LDAPConstants.DEFAULT_BATCH_SIZE_FOR_SYNC;
boolean nextPage = true;
while (nextPage) {
userQuery.setLimit(pageSize);
final List<LDAPObject> users = userQuery.getResultList();
nextPage = userQuery.getPaginationContext() != null;
UserFederationSyncResult currentPageSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
syncResult.add(currentPageSync);
}
} else {
// LDAP pagination not available. Do everything in single transaction
final List<LDAPObject> users = userQuery.getResultList();
UserFederationSyncResult currentSync = importLdapUsers(sessionFactory, realmId, fedModel, users);
syncResult.add(currentSync);
}
return syncResult;
}
private LDAPQuery createQuery(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel model) {
class QueryHolder {
LDAPQuery query;
}
final QueryHolder queryHolder = new QueryHolder();
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, model);
RealmModel realm = session.realms().getRealm(realmId);
queryHolder.query = LDAPUtils.createQueryForUserSearch(ldapFedProvider, realm);
}
});
return queryHolder.query;
}
protected UserFederationSyncResult importLdapUsers(KeycloakSessionFactory sessionFactory, final String realmId, final UserFederationProviderModel fedModel, List<LDAPObject> ldapUsers) {
final UserFederationSyncResult syncResult = new UserFederationSyncResult();
class BooleanHolder {
private boolean value = true;
}
final BooleanHolder exists = new BooleanHolder();
for (final LDAPObject ldapUser : ldapUsers) {
try {
// Process each user in it's own transaction to avoid global fail
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
RealmModel currentRealm = session.realms().getRealm(realmId);
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
UserModel currentUser = session.userStorage().getUserByUsername(username, currentRealm);
if (currentUser == null) {
// Add new user to Keycloak
exists.value = false;
ldapFedProvider.importUserFromLDAP(session, currentRealm, ldapUser);
syncResult.increaseAdded();
} else {
if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getFirstAttribute(LDAPConstants.LDAP_ID)))) {
// Update keycloak user
Set<UserFederationMapperModel> federationMappers = currentRealm.getUserFederationMappersByFederationProvider(fedModel.getId());
for (UserFederationMapperModel mapperModel : federationMappers) {
LDAPFederationMapper ldapMapper = ldapFedProvider.getMapper(mapperModel);
ldapMapper.onImportUserFromLDAP(mapperModel, ldapFedProvider, ldapUser, currentUser, currentRealm, false);
}
logger.debugf("Updated user from LDAP: %s", currentUser.getUsername());
syncResult.increaseUpdated();
} else {
logger.warnf("User '%s' is not updated during sync as he already exists in Keycloak database but is not linked to federation provider '%s'", username, fedModel.getDisplayName());
syncResult.increaseFailed();
}
}
}
});
} catch (ModelException me) {
logger.error("Failed during import user from LDAP", me);
syncResult.increaseFailed();
// Remove user if we already added him during this transaction
if (!exists.value) {
KeycloakModelUtils.runJobInTransaction(sessionFactory, new KeycloakSessionTask() {
@Override
public void run(KeycloakSession session) {
LDAPFederationProvider ldapFedProvider = getInstance(session, fedModel);
RealmModel currentRealm = session.realms().getRealm(realmId);
String username = LDAPUtils.getUsername(ldapUser, ldapFedProvider.getLdapIdentityStore().getConfig());
UserModel existing = session.userStorage().getUserByUsername(username, currentRealm);
if (existing != null) {
session.userStorage().removeUser(currentRealm, existing);
}
}
});
}
}
}
return syncResult;
}
protected SPNEGOAuthenticator createSPNEGOAuthenticator(String spnegoToken, CommonKerberosConfig kerberosConfig) {
KerberosServerSubjectAuthenticator kerberosAuth = createKerberosSubjectAuthenticator(kerberosConfig);
return new SPNEGOAuthenticator(kerberosConfig, kerberosAuth, spnegoToken);
}
protected KerberosServerSubjectAuthenticator createKerberosSubjectAuthenticator(CommonKerberosConfig kerberosConfig) {
return new KerberosServerSubjectAuthenticator(kerberosConfig);
}
protected KerberosUsernamePasswordAuthenticator createKerberosUsernamePasswordAuthenticator(CommonKerberosConfig kerberosConfig) {
return new KerberosUsernamePasswordAuthenticator(kerberosConfig);
}
}
@@ -1,49 +1,49 @@
package org.keycloak.federation.ldap;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
protected LDAPFederationProvider provider;
public ReadonlyLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
super(delegate);
this.provider = provider;
}
@Override
public void setUsername(String username) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void setLastName(String lastName) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void setFirstName(String first) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
delegate.updateCredential(cred);
}
@Override
public void setEmail(String email) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
}
package org.keycloak.federation.ldap;
import org.keycloak.models.ModelReadOnlyException;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class ReadonlyLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
protected LDAPFederationProvider provider;
public ReadonlyLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
super(delegate);
this.provider = provider;
}
@Override
public void setUsername(String username) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void setLastName(String lastName) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void setFirstName(String first) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
delegate.updateCredential(cred);
}
@Override
public void setEmail(String email) {
throw new ModelReadOnlyException("Federated storage is not writable");
}
}
@@ -1,20 +1,20 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UnsyncedLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
private static final Logger logger = Logger.getLogger(UnsyncedLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
public UnsyncedLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
super(delegate);
this.provider = provider;
}
}
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class UnsyncedLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
private static final Logger logger = Logger.getLogger(UnsyncedLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
public UnsyncedLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider) {
super(delegate);
this.provider = provider;
}
}
@@ -1,42 +1,42 @@
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class WritableLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
protected LDAPObject ldapObject;
public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
super(delegate);
this.provider = provider;
this.ldapObject = ldapObject;
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (!provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
delegate.updateCredential(cred);
return;
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
String password = cred.getValue();
ldapIdentityStore.updatePassword(ldapObject, password);
} else {
logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
}
}
}
package org.keycloak.federation.ldap;
import org.jboss.logging.Logger;
import org.keycloak.federation.ldap.idm.model.LDAPObject;
import org.keycloak.federation.ldap.idm.store.ldap.LDAPIdentityStore;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.UserModelDelegate;
/**
* @author <a href="mailto:bill@burkecentral.com">Bill Burke</a>
* @version $Revision: 1 $
*/
public class WritableLDAPUserModelDelegate extends UserModelDelegate implements UserModel {
private static final Logger logger = Logger.getLogger(WritableLDAPUserModelDelegate.class);
protected LDAPFederationProvider provider;
protected LDAPObject ldapObject;
public WritableLDAPUserModelDelegate(UserModel delegate, LDAPFederationProvider provider, LDAPObject ldapObject) {
super(delegate);
this.provider = provider;
this.ldapObject = ldapObject;
}
@Override
public void updateCredential(UserCredentialModel cred) {
if (!provider.getSupportedCredentialTypes(delegate).contains(cred.getType())) {
delegate.updateCredential(cred);
return;
}
if (cred.getType().equals(UserCredentialModel.PASSWORD)) {
LDAPIdentityStore ldapIdentityStore = provider.getLdapIdentityStore();
String password = cred.getValue();
ldapIdentityStore.updatePassword(ldapObject, password);
} else {
logger.warnf("Don't know how to update credential of type [%s] for user [%s]", cred.getType(), delegate.getUsername());
}
}
}