Something went wrong on our end
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LDAPLoginManagerImpl.java 60.11 KiB
/**
* <a href="http://www.openolat.org">
* OpenOLAT - Online Learning and Training</a><br>
* <p>
* Licensed under the Apache License, Version 2.0 (the "License"); <br>
* you may not use this file except in compliance with the License.<br>
* You may obtain a copy of the License at the
* <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
* <p>
* Unless required by applicable law or agreed to in writing,<br>
* software distributed under the License is distributed on an "AS IS" BASIS, <br>
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
* See the License for the specific language governing permissions and <br>
* limitations under the License.
* <p>
* Initial code contributed and copyrighted by<br>
* frentix GmbH, http://www.frentix.com
* <p>
*/
package org.olat.ldap.manager;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.Control;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import org.apache.commons.lang.ArrayUtils;
import org.olat.admin.user.delete.service.UserDeletionManager;
import org.olat.basesecurity.Authentication;
import org.olat.basesecurity.BaseSecurity;
import org.olat.basesecurity.BaseSecurityModule;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.GroupRoles;
import org.olat.basesecurity.IdentityRef;
import org.olat.basesecurity.SecurityGroup;
import org.olat.basesecurity.model.IdentityRefImpl;
import org.olat.core.CoreSpringFactory;
import org.olat.core.commons.persistence.DB;
import org.olat.core.commons.services.taskexecutor.TaskExecutorManager;
import org.olat.core.gui.control.Event;
import org.olat.core.id.Identity;
import org.olat.core.id.Roles;
import org.olat.core.id.User;
import org.olat.core.id.UserConstants;
import org.olat.core.logging.OLog;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.core.util.WorkThreadInformations;
import org.olat.core.util.coordinate.Coordinator;
import org.olat.core.util.coordinate.CoordinatorManager;
import org.olat.core.util.event.FrameworkStartedEvent;
import org.olat.core.util.event.FrameworkStartupEventChannel;
import org.olat.core.util.event.GenericEventListener;
import org.olat.core.util.mail.MailHelper;
import org.olat.group.BusinessGroup;
import org.olat.group.BusinessGroupManagedFlag;
import org.olat.group.BusinessGroupService;
import org.olat.group.manager.BusinessGroupRelationDAO;
import org.olat.group.model.SearchBusinessGroupParams;
import org.olat.ldap.LDAPConstants;
import org.olat.ldap.LDAPError;
import org.olat.ldap.LDAPEvent;
import org.olat.ldap.LDAPLoginManager;
import org.olat.ldap.LDAPLoginModule;
import org.olat.ldap.LDAPSyncConfiguration;
import org.olat.ldap.model.LDAPGroup;
import org.olat.ldap.model.LDAPUser;
import org.olat.ldap.ui.LDAPAuthenticationController;
import org.olat.login.auth.OLATAuthManager;
import org.olat.user.UserManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
/**
* Description: This manager handles communication between LDAP and OLAT. LDAP access is done by JNDI.
* The synching is done only on node 1 of a cluster.
* <p>
* LDAPLoginMangerImpl
* <p>
*
* @author Maurus Rohrer
*/
@Service("org.olat.ldap.LDAPLoginManager")
public class LDAPLoginManagerImpl implements LDAPLoginManager, GenericEventListener {
private static final OLog log = Tracing.createLoggerFor(LDAPLoginManagerImpl.class);
private static final String TIMEOUT_KEY = "com.sun.jndi.ldap.connect.timeout";
private static boolean batchSyncIsRunning = false;
private static Date lastSyncDate = null; // first sync is always a full sync
private Coordinator coordinator;
private TaskExecutorManager taskExecutorManager;
@Autowired
private DB dbInstance;
@Autowired
private LDAPDAO ldapDao;
@Autowired
private UserManager userManager;
@Autowired
private BaseSecurity securityManager;
@Autowired
private LDAPLoginModule ldapLoginModule;
@Autowired
private LDAPSyncConfiguration syncConfiguration;
@Autowired
private UserDeletionManager userDeletionManager;
@Autowired
private BusinessGroupService businessGroupService;
@Autowired
private BusinessGroupRelationDAO businessGroupRelationDao;
@Autowired
public LDAPLoginManagerImpl(CoordinatorManager coordinatorManager, TaskExecutorManager taskExecutorManager) {
this.coordinator = coordinatorManager.getCoordinator();
this.taskExecutorManager = taskExecutorManager;
coordinator.getEventBus().registerFor(this, null, ldapSyncLockOres);
FrameworkStartupEventChannel.registerForStartupEvent(this);
}
@Override
public void event(Event event) {
if(event instanceof LDAPEvent) {
if(LDAPEvent.SYNCHING.equals(event.getCommand())) {
batchSyncIsRunning = true;
} else if(LDAPEvent.SYNCHING_ENDED.equals(event.getCommand())) {
batchSyncIsRunning = false;
lastSyncDate = ((LDAPEvent)event).getTimestamp();
} else if(LDAPEvent.DO_SYNCHING.equals(event.getCommand())) {
doHandleBatchSync();
}
} else if(event instanceof FrameworkStartedEvent) {
try {
init();
} catch (Exception e) {
log.error("", e);
}
}
}
private void init() {
if(ldapLoginModule.isLDAPEnabled()) {
if (bindSystem() == null) {
// don't disable ldap, maybe just a temporary problem, but still report
// problem in logfile
log.error("LDAP connection test failed during module initialization, edit config or contact network administrator");
} else {
log.info("LDAP login is enabled");
}
// Start LDAP cron sync job
if (ldapLoginModule.isLdapSyncCronSync()) {
LDAPError errors = new LDAPError();
if (doBatchSync(errors)) {
log.info("LDAP start sync: users synced");
} else {
log.warn("LDAP start sync error: " + errors.get());
}
} else {
log.info("LDAP cron sync is disabled");
}
}
}
private void doHandleBatchSync() {
//fxdiff: also run on nodes != 1 as nodeid = tomcat-id in fx-environment
// if(WebappHelper.getNodeId() != 1) return;
Runnable batchSyncTask = new Runnable() {
@Override
public void run() {
LDAPError errors = new LDAPError();
doBatchSync(errors);
}
};
taskExecutorManager.execute(batchSyncTask);
}
/**
* Connect to the LDAP server with System DN and Password
*
* Configuration: LDAP URL = ldapContext.xml (property=ldapURL) System DN =
* ldapContext.xml (property=ldapSystemDN) System PW = ldapContext.xml
* (property=ldapSystemPW)
*
* @return The LDAP connection (LdapContext) or NULL if connect fails
*
* @throws NamingException
*/
public LdapContext bindSystem() {
// set LDAP connection attributes
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapLoginModule.getLdapUrl());
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, ldapLoginModule.getLdapSystemDN());
env.put(Context.SECURITY_CREDENTIALS, ldapLoginModule.getLdapSystemPW());
if(ldapLoginModule.getLdapConnectionTimeout() != null) {
env.put(TIMEOUT_KEY, ldapLoginModule.getLdapConnectionTimeout().toString());
}
// check ssl
if (ldapLoginModule.isSslEnabled()) {
enableSSL(env);
}
try {
InitialLdapContext ctx = new InitialLdapContext(env, new Control[]{});
ctx.getConnectControls();
return ctx;
} catch (NamingException e) {
log.error("NamingException when trying to bind system with DN::" + ldapLoginModule.getLdapSystemDN() + " and PW::"
+ ldapLoginModule.getLdapSystemPW() + " on URL::" + ldapLoginModule.getLdapUrl(), e);
return null;
} catch (Exception e) {
log.error("Exception when trying to bind system with DN::" + ldapLoginModule.getLdapSystemDN() + " and PW::"
+ ldapLoginModule.getLdapSystemPW() + " on URL::" + ldapLoginModule.getLdapUrl(), e);
return null;
}
}
/**
*
* Connect to LDAP with the User-Name and Password given as parameters
*
* Configuration: LDAP URL = ldapContext.xml (property=ldapURL) LDAP Base =
* ldapContext.xml (property=ldapBase) LDAP Attributes Map =
* ldapContext.xml (property=userAttrs)
*
*
* @param uid The users LDAP login name (can't be null)
* @param pwd The users LDAP password (can't be null)
*
* @return After successful bind Attributes otherwise NULL
*
* @throws NamingException
*/
@Override
public Attributes bindUser(String login, String pwd, LDAPError errors) {
// get user name, password and attributes
String ldapUrl = ldapLoginModule.getLdapUrl();
String[] userAttr = syncConfiguration.getUserAttributes();
if (login == null || pwd == null) {
if (log.isDebug()) log.debug("Error when trying to bind user, missing username or password. Username::" + login + " pwd::" + pwd);
errors.insert("Username and password must be selected");
return null;
}
LdapContext ctx = bindSystem();
if (ctx == null) {
errors.insert("LDAP connection error");
return null;
}
String userDN = ldapDao.searchUserForLogin(login, ctx);
if (userDN == null) {
log.info("Error when trying to bind user with username::" + login + " - user not found on LDAP server"
+ (ldapLoginModule.isCacheLDAPPwdAsOLATPwdOnLogin() ? ", trying with OLAT login provider" : ""));
errors.insert("Username or password incorrect");
return null;
}
// Ok, so far so good, user exists. Now try to fetch attributes using the
// users credentials
Hashtable<String, String> env = new Hashtable<String, String>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, ldapUrl);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, userDN);
env.put(Context.SECURITY_CREDENTIALS, pwd);
if(ldapLoginModule.getLdapConnectionTimeout() != null) {
env.put(TIMEOUT_KEY, ldapLoginModule.getLdapConnectionTimeout().toString());
}
if(ldapLoginModule.isSslEnabled()) {
enableSSL(env);
}
try {
Control[] connectCtls = new Control[]{};
LdapContext userBind = new InitialLdapContext(env, connectCtls);
Attributes attributes = userBind.getAttributes(userDN, userAttr);
userBind.close();
return attributes;
} catch (AuthenticationException e) {
log.info("Error when trying to bind user with username::" + login + " - invalid LDAP password");
errors.insert("Username or password incorrect");
return null;
} catch (NamingException e) {
log.error("NamingException when trying to get attributes after binding user with username::" + login, e);
errors.insert("Username or password incorrect");
return null;
}
}
@Override
public Identity authenticate(String username, String pwd, LDAPError ldapError) {
long start = System.nanoTime();
//authenticate against LDAP server
Attributes attrs = bindUser(username, pwd, ldapError);
long takes = System.nanoTime() - start;
if(takes > LDAPLoginModule.WARNING_LIMIT) {
log.warn("LDAP Authentication takes (ms): " + (takes / 1000000));
}
if (ldapError.isEmpty() && attrs != null) {
Identity identity = findIdentityByLdapAuthentication(attrs, ldapError);
if (!ldapError.isEmpty()) {
return null;
}
if (identity == null) {
if(ldapLoginModule.isCreateUsersOnLogin()) {
// User authenticated but not yet existing - create as new OLAT user
createAndPersistUser(attrs);
identity = findIdentityByLdapAuthentication(attrs, ldapError);
} else {
ldapError.insert("login.notauthenticated");
}
} else {
// User does already exist - just sync attributes
Map<String, String> olatProToSync = prepareUserPropertyForSync(attrs, identity);
if (olatProToSync != null) {
syncUser(olatProToSync, identity);
}
}
// Add or update an OLAT authentication token for this user if configured in the module
if (identity != null && ldapLoginModule.isCacheLDAPPwdAsOLATPwdOnLogin()) {
// there is no WEBDAV token but an HA1, the HA1 is linked to the OLAT one.
CoreSpringFactory.getImpl(OLATAuthManager.class)
.synchronizeOlatPasswordAndUsername(identity, identity, username, pwd);
}
return identity;
}
return null;
}
/**
* Change the password on the LDAP server.
* @see org.olat.ldap.LDAPLoginManager#changePassword(org.olat.core.id.Identity, java.lang.String, org.olat.ldap.LDAPError)
*/
@Override
public boolean changePassword(Identity identity, String pwd, LDAPError errors) {
String uid = identity.getName();
String ldapUserPasswordAttribute = syncConfiguration.getLdapUserPasswordAttribute();
try {
LdapContext ctx = bindSystem();
String dn = ldapDao.searchUserDNByUid(uid, ctx);
List<ModificationItem> modificationItemList = new ArrayList<>();
if(ldapLoginModule.isActiveDirectory()) {
boolean resetLockoutTime = false;
if(ldapLoginModule.isResetLockTimoutOnPasswordChange()) {
String[] attrs = syncConfiguration.getUserAttributes();
List<String> attrList = new ArrayList<>(Arrays.asList(attrs));
attrList.add("lockoutTime");
attrs = attrList.toArray(new String[attrList.size()]);
Attributes attributes = ctx.getAttributes(dn, attrs);
Attribute lockoutTimeAttr = attributes.get("lockoutTime");
if(lockoutTimeAttr != null && lockoutTimeAttr.size() > 0) {
Object lockoutTime = lockoutTimeAttr.get();
if(lockoutTime != null && !lockoutTime.equals("0")) {
resetLockoutTime = true;
}
}
}
//active directory need the password enquoted and unicoded (but little-endian)
String quotedPassword = "\"" + pwd + "\"";
char unicodePwd[] = quotedPassword.toCharArray();
byte pwdArray[] = new byte[unicodePwd.length * 2];
for (int i=0; i<unicodePwd.length; i++) {
pwdArray[i*2 + 1] = (byte) (unicodePwd[i] >>> 8);
pwdArray[i*2 + 0] = (byte) (unicodePwd[i] & 0xff);
}
BasicAttribute userPasswordAttribute = new BasicAttribute(ldapUserPasswordAttribute, pwdArray );
modificationItemList.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userPasswordAttribute));
if(resetLockoutTime) {
BasicAttribute lockTimeoutAttribute = new BasicAttribute("lockoutTime", "0");
modificationItemList.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, lockTimeoutAttribute));
}
} else {
BasicAttribute userPasswordAttribute = new BasicAttribute(ldapUserPasswordAttribute, pwd);
modificationItemList.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE, userPasswordAttribute));
}
ModificationItem[] modificationItems = modificationItemList.toArray(new ModificationItem[modificationItemList.size()]);
ctx.modifyAttributes(dn, modificationItems);
ctx.close();
return true;
} catch (NamingException e) {
log.error("NamingException when trying to change password with username::" + uid, e);
errors.insert("Cannot change the password");
return false;
} catch(Exception e) {
log.error("Unexpected exception when trying to change password with username::" + uid, e);
errors.insert("Cannot change the password");
return false;
}
}
/**
* Delete all Identities in List and removes them from LDAPSecurityGroup
*
* @param identityList List of Identities to delete
*/
@Override
public void deletIdentities(List<Identity> identityList) {
SecurityGroup secGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
for (Identity identity: identityList) {
securityManager.removeIdentityFromSecurityGroup(identity, secGroup);
userDeletionManager.deleteIdentity(identity);
dbInstance.intermediateCommit();
}
}
/**
* Sync all OLATPropertys in Map of Identity
*
* @param olatPropertyMap Map of changed OLAT properties
* (OLATProperty,LDAPValue)
* @param identity Identity to sync
*/
@Override
public Identity syncUser(Map<String, String> olatPropertyMap, IdentityRef identityRef) {
if (identityRef == null) {
log.warn("Identiy is null - should not happen", null);
return null;
}
Identity identity = securityManager.loadIdentityByKey(identityRef.getKey());
User user = identity.getUser();
// remove user identifyer - can not be changed later
olatPropertyMap.remove(LDAPConstants.LDAP_USER_IDENTIFYER);
// remove attributes that are defined as sync-only-on-create
Set<String> syncOnlyOnCreateProperties = syncConfiguration.getSyncOnlyOnCreateProperties();
if (syncOnlyOnCreateProperties != null) {
for (String syncOnlyOnCreateKey : syncOnlyOnCreateProperties) {
olatPropertyMap.remove(syncOnlyOnCreateKey);
}
}
for(Map.Entry<String, String> keyValuePair : olatPropertyMap.entrySet()) {
String propName = keyValuePair.getKey();
String value = keyValuePair.getValue();
if(value == null) {
if(user.getProperty(propName, null) != null) {
log.debug("removed property " + propName + " for identity " + identity);
user.setProperty(propName, value);
}
} else {
user.setProperty(propName, value);
}
}
// Add static user properties from the configuration
Map<String, String> staticProperties = syncConfiguration.getStaticUserProperties();
if (staticProperties != null && staticProperties.size() > 0) {
for (Map.Entry<String, String> staticProperty : staticProperties.entrySet()) {
user.setProperty(staticProperty.getKey(), staticProperty.getValue());
}
}
userManager.updateUser(user);
return identity;
}
@Override
public Identity createAndPersistUser(String uid) {
String ldapUserIDAttribute = syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
String filter = ldapDao.buildSearchUserFilter(ldapUserIDAttribute, uid);
LdapContext ctx = bindSystem();
String userDN = ldapDao.searchUserDNByUid(uid, ctx);
log.info("create and persist user identifier by userDN: " + userDN + " with filter: " + filter);
LDAPUserVisitor visitor = new LDAPUserVisitor(syncConfiguration);
ldapDao.search(visitor, userDN, filter, syncConfiguration.getUserAttributes(), ctx);
Identity newIdentity = null;
List<LDAPUser> ldapUser = visitor.getLdapUserList();
if(ldapUser != null && ldapUser.size() > 0) {
Attributes userAttributes = ldapUser.get(0).getAttributes();
newIdentity = createAndPersistUser(userAttributes);
}
return newIdentity;
}
/**
* Creates User in OLAT and ads user to LDAP securityGroup Required Attributes
* have to be checked before this method.
*
* @param userAttributes Set of LDAP Attribute of User to be created
*/
@Override
public Identity createAndPersistUser(Attributes userAttributes) {
// Get and Check Config
String[] reqAttrs = syncConfiguration.checkRequestAttributes(userAttributes);
if (reqAttrs != null) {
log.warn("Can not create and persist user, the following attributes are missing::" + ArrayUtils.toString(reqAttrs), null);
return null;
}
String uid = getAttributeValue(userAttributes.get(syncConfiguration
.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER)));
String email = getAttributeValue(userAttributes.get(syncConfiguration.getOlatPropertyToLdapAttribute(UserConstants.EMAIL)));
// Lookup user
if (securityManager.findIdentityByNameCaseInsensitive(uid) != null) {
log.error("Can't create user with username='" + uid + "', this username does already exist in OLAT database", null);
return null;
}
if (!MailHelper.isValidEmailAddress(email)) {
// needed to prevent possibly an AssertException in findIdentityByEmail breaking the sync!
log.error("Cannot try to lookup user " + uid + " by email with an invalid email::" + email, null);
return null;
}
if (userManager.userExist(email) ) {
log.error("Can't create user with email='" + email + "', a user with that email does already exist in OLAT database", null);
return null;
}
// Create User (first and lastname is added in next step)
User user = userManager.createUser(null, null, email);
// Set User Property's (Iterates over Attributes and gets OLAT Property out
// of olatexconfig.xml)
NamingEnumeration<? extends Attribute> neAttr = userAttributes.getAll();
try {
while (neAttr.hasMore()) {
Attribute attr = neAttr.next();
String olatProperty = mapLdapAttributeToOlatProperty(attr.getID());
if (!attr.getID().equalsIgnoreCase(syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER)) ) {
String ldapValue = getAttributeValue(attr);
if (olatProperty == null || ldapValue == null) continue;
user.setProperty(olatProperty, ldapValue);
}
}
// Add static user properties from the configuration
Map<String, String> staticProperties = syncConfiguration.getStaticUserProperties();
if (staticProperties != null && staticProperties.size() > 0) {
for (Entry<String, String> staticProperty : staticProperties.entrySet()) {
user.setProperty(staticProperty.getKey(), staticProperty.getValue());
}
}
} catch (NamingException e) {
log.error("NamingException when trying to create and persist LDAP user with username::" + uid, e);
return null;
} catch (Exception e) {
// catch any exception here to properly log error
log.error("Unknown exception when trying to create and persist LDAP user with username::" + uid, e);
return null;
}
// Create Identity
Identity identity = securityManager.createAndPersistIdentityAndUser(uid, null, user, LDAPAuthenticationController.PROVIDER_LDAP, uid);
// Add to SecurityGroup LDAP
SecurityGroup secGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
securityManager.addIdentityToSecurityGroup(identity, secGroup);
// Add to SecurityGroup OLATUSERS
secGroup = securityManager.findSecurityGroupByName(Constants.GROUP_OLATUSERS);
securityManager.addIdentityToSecurityGroup(identity, secGroup);
log.info("Created LDAP user username::" + uid);
return identity;
}
/**
* Checks if LDAP properties are different then OLAT properties of a User. If
* they are different a Map (OlatPropertyName,LDAPValue) is returned.
*
* @param attributes Set of LDAP Attribute of Identity
* @param identity Identity to compare
*
* @return Map(OlatPropertyName,LDAPValue) of properties Identity, where
* property has changed. NULL is returned it no attributes have to be synced
*/
@SuppressWarnings("unchecked")
public Map<String, String> prepareUserPropertyForSync(Attributes attributes, Identity identity) {
Map<String, String> olatPropertyMap = new HashMap<String, String>();
User user = identity.getUser();
NamingEnumeration<Attribute> neAttrs = (NamingEnumeration<Attribute>) attributes.getAll();
try {
while (neAttrs.hasMore()) {
Attribute attr = neAttrs.next();
String olatProperty = mapLdapAttributeToOlatProperty(attr.getID());
if(olatProperty == null) {
continue;
}
String ldapValue = getAttributeValue(attr);
String olatValue = user.getProperty(olatProperty, null);
if (olatValue == null) {
// new property or user ID (will always be null, pseudo property)
olatPropertyMap.put(olatProperty, ldapValue);
} else {
if (ldapValue.compareTo(olatValue) != 0) {
olatPropertyMap.put(olatProperty, ldapValue);
}
}
}
if (olatPropertyMap.size() == 1 && olatPropertyMap.get(LDAPConstants.LDAP_USER_IDENTIFYER) != null) {
log.debug("propertymap for identity " + identity.getName() + " contains only userID, NOTHING TO SYNC!");
return null;
} else {
log.debug("propertymap for identity " + identity.getName() + " contains " + olatPropertyMap.size() + " items (" + olatPropertyMap.keySet() + ") to be synced later on");
return olatPropertyMap;
}
} catch (NamingException e) {
log.error("NamingException when trying to prepare user properties for LDAP sync", e);
return null;
}
}
/**
* Maps LDAP Attributes to the OLAT Property
*
* Configuration: LDAP Attributes Map = ldapContext.xml (property=userAttrs)
*
* @param attrID LDAP Attribute
* @return OLAT Property
*/
private String mapLdapAttributeToOlatProperty(String attrID) {
Map<String, String> userAttrMapper = syncConfiguration.getUserAttributeMap();
String olatProperty = userAttrMapper.get(attrID);
return olatProperty;
}
/**
* Extracts Value out of LDAP Attribute
*
*
* @param attribute LDAP Naming Attribute
* @return String value of Attribute, null on Exception
*
* @throws NamingException
*/
private String getAttributeValue(Attribute attribute) {
try {
String attrValue = (String)attribute.get();
return attrValue;
} catch (NamingException e) {
log.error("NamingException when trying to get attribute value for attribute::" + attribute, e);
return null;
}
}
/**
* The method search in LDAP the user, search the groups
* of which it is member of, and sync the groups.
*
* @param identity The identity to sync
*/
@Override
public void syncUserGroups(Identity identity) {
LdapContext ctx = bindSystem();
if (ctx == null) {
log.error("could not bind to ldap", null);
}
String ldapUserIDAttribute = syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
String filter = ldapDao.buildSearchUserFilter(ldapUserIDAttribute, identity.getName());
boolean withCoacheOfGroups = StringHelper.containsNonWhitespace(syncConfiguration.getCoachedGroupAttribute());
List<String> ldapBases = syncConfiguration.getLdapBases();
String[] searchAttr;
if(withCoacheOfGroups) {
searchAttr = new String[]{ "dn", syncConfiguration.getCoachedGroupAttribute() };
} else {
searchAttr = new String[]{ "dn" };
}
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(searchAttr);
String userDN = null;
List<String> groupList = null;
for (String ldapBase : ldapBases) {
try {
NamingEnumeration<SearchResult> enm = ctx.search(ldapBase, filter, ctls);
while (enm.hasMore()) {
SearchResult result = enm.next();
userDN = result.getNameInNamespace();
if(withCoacheOfGroups) {
Attributes resAttributes = result.getAttributes();
Attribute coachOfGroupsAttr = resAttributes.get(syncConfiguration.getCoachedGroupAttribute());
if(coachOfGroupsAttr != null && coachOfGroupsAttr.get() instanceof String) {
String groupString = (String)coachOfGroupsAttr.get();
if(!"-".equals(groupString)) {
String[] groupArr = groupString.split(syncConfiguration.getCoachedGroupAttributeSeparator());
groupList = new ArrayList<>(groupArr.length);
for(String group:groupArr) {
groupList.add(group);
}
}
}
}
}
if (userDN != null) {
break;
}
} catch (NamingException e) {
log.error("NamingException when trying to bind user with username::" + identity.getName() + " on ldapBase::" + ldapBase, e);
}
}
// get the potential groups
if(userDN != null) {
List<String> groupDNs = syncConfiguration.getLdapGroupBases();
String groupFilter = "(&(objectClass=groupOfNames)(member=" + userDN + "))";
List<LDAPGroup> groups = ldapDao.searchGroups(ctx, groupDNs, groupFilter);
for(LDAPGroup group:groups) {
BusinessGroup managedGroup = getManagerBusinessGroup(group.getCommonName());
if(managedGroup != null) {
List<String> roles = businessGroupRelationDao.getRoles(identity, managedGroup);
if(roles.isEmpty()) {
boolean coach = groupList != null && groupList.contains(group.getCommonName());
if(coach) {
businessGroupRelationDao.addRole(identity, managedGroup, GroupRoles.coach.name());
} else {
businessGroupRelationDao.addRole(identity, managedGroup, GroupRoles.participant.name());
}
}
}
}
}
}
/**
* Searches for Identity in OLAT.
*
* @param uid Name of Identity
* @param errors LDAPError Object if user exits but not member of
* LDAPSecurityGroup
*
* @return Identity if it's found and member of LDAPSecurityGroup, null
* otherwise (if user exists but not managed by LDAP, error Object is
* modified)
*/
@Override
public Identity findIdentityByLdapAuthentication(Attributes attrs, LDAPError errors) {
if(attrs == null) {
errors.insert("findIdentyByLdapAuthentication: attrs::null");
return null;
}
String uid = getAttributeValue(attrs.get(syncConfiguration
.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER)));
String token = getAttributeValue(attrs.get(syncConfiguration.getLdapUserLoginAttribute()));
Identity identity = securityManager.findIdentityByNameCaseInsensitive(uid);
if (identity == null) {
return null;
} else {
SecurityGroup ldapGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
if (ldapGroup == null) {
log.error("Error getting user from OLAT security group '" + LDAPConstants.SECURITY_GROUP_LDAP + "' : group does not exist", null);
return null;
}
boolean inSecurityGroup = securityManager.isIdentityInSecurityGroup(identity, ldapGroup);
if (inSecurityGroup) {
Authentication ldapAuth = securityManager.findAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP);
if(ldapAuth == null) {
//BUG Fixe: update the user and test if it has a ldap provider
securityManager.createAndPersistAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP, token, null, null);
} else if(StringHelper.containsNonWhitespace(token) && !token.equals(ldapAuth.getAuthusername())) {
ldapAuth.setAuthusername(token);
ldapAuth = securityManager.updateAuthentication(ldapAuth);
}
return identity;
} else if (ldapLoginModule.isConvertExistingLocalUsersToLDAPUsers()) {
// Add user to LDAP security group and add the ldap provider
securityManager.createAndPersistAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP, token, null, null);
securityManager.addIdentityToSecurityGroup(identity, ldapGroup);
log.info("Found identity by LDAP username that was not yet in LDAP security group. Converted user::" + uid
+ " to be an LDAP managed user");
return identity;
} else {
errors.insert("findIdentyByLdapAuthentication: User with username::" + uid + " exist but not Managed by LDAP");
return null;
}
}
}
/**
*
* Creates list of all OLAT Users which have been deleted out of the LDAP
* directory but still exits in OLAT
*
* Configuration: Required Attributes = ldapContext.xml (property=reqAttrs)
* LDAP Base = ldapContext.xml (property=ldapBase)
*
* @param syncTime The time to search in LDAP for changes since this time.
* SyncTime has to formatted: JJJJMMddHHmm
* @param ctx The LDAP system connection, if NULL or closed NamingExecpiton is
* thrown
*
* @return Returns list of Identity from the user which have been deleted in
* LDAP
*
* @throws NamingException
*/
public List<Identity> getIdentitysDeletedInLdap(LdapContext ctx) {
if (ctx == null) return null;
// Find all LDAP Users
String userID = syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
String userFilter = syncConfiguration.getLdapUserFilter();
final List<String> ldapList = new ArrayList<String>();
ldapDao.searchInLdap(new LDAPVisitor() {
@Override
public void visit(SearchResult result) throws NamingException {
Attributes attrs = result.getAttributes();
NamingEnumeration<? extends Attribute> aEnum = attrs.getAll();
while (aEnum.hasMore()) {
Attribute attr = aEnum.next();
// use lowercase username
ldapList.add(attr.get().toString().toLowerCase());
}
}
}, (userFilter == null ? "" : userFilter), new String[] { userID }, ctx);
if (ldapList.isEmpty()) {
log.warn("No users in LDAP found, can't create deletionList!!", null);
return null;
}
// Find all User in OLAT, members of LDAPSecurityGroup
SecurityGroup ldapGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
if (ldapGroup == null) {
log.error("Error getting users from OLAT security group '" + LDAPConstants.SECURITY_GROUP_LDAP + "' : group does not exist", null);
return null;
}
List<Identity> identityListToDelete = new ArrayList<Identity>();
List<Identity> olatListIdentity = securityManager.getIdentitiesOfSecurityGroup(ldapGroup);
for (Identity ida:olatListIdentity) {
// compare usernames with lowercase
if (!ldapList.contains(ida.getName().toLowerCase())) {
identityListToDelete.add(ida);
}
}
return identityListToDelete;
}
/**
* Execute Batch Sync. Will update all Attributes of LDAP users in OLAt, create new users and delete users in OLAT.
* Can be configured in ldapContext.xml
*
* @param LDAPError
*
*/
@Override
public boolean doBatchSync(LDAPError errors) {
//fxdiff: also run on nodes != 1 as nodeid = tomcat-id in fx-environment
// if(WebappHelper.getNodeId() != 1) {
// log.warn("Sync happens only on node 1", null);
// return false;
// }
// o_clusterNOK
// Synchronize on class so that only one thread can read the
// batchSyncIsRunning flag Only this read operation is synchronized to not
// block the whole execution of the do BatchSync method. The method is used
// in automatic cron scheduler job and also in GUI controllers that can't
// wait for the concurrent running request to finish first, an immediate
// feedback about the concurrent job is needed. -> only synchronize on the
// property read.
synchronized (LDAPLoginManagerImpl.class) {
if (batchSyncIsRunning) {
// don't run twice, skip this execution
log.info("LDAP user doBatchSync started, but another job is still running - skipping this sync");
errors.insert("BatchSync already running by concurrent process");
return false;
}
}
WorkThreadInformations.setLongRunningTask("ldapSync");
coordinator.getEventBus().fireEventToListenersOf(new LDAPEvent(LDAPEvent.SYNCHING), ldapSyncLockOres);
lastSyncDate = null;
LdapContext ctx = null;
boolean success = false;
try {
acquireSyncLock();
long startTime = System.currentTimeMillis();
ctx = bindSystem();
if (ctx == null) {
errors.insert("LDAP connection ERROR");
log.error("Error in LDAP batch sync: LDAP connection empty", null);
freeSyncLock();
success = false;
return success;
}
Date timeBeforeSync = new Date();
//check server capabilities
// Get time before sync to have a save sync time when sync is successful
String sinceSentence = (lastSyncDate == null ? "" : " since last sync from " + lastSyncDate);
doBatchSyncDeletedUsers(ctx, sinceSentence);
// bind again to use an initial unmodified context. lookup of server-properties might fail otherwise!
ctx.close();
ctx = bindSystem();
Map<String,LDAPUser> dnToIdentityKeyMap = new HashMap<>();
List<LDAPUser> ldapUsers = doBatchSyncNewAndModifiedUsers(ctx, sinceSentence, dnToIdentityKeyMap, errors);
ctx.close();
ctx = bindSystem();
//sync groups by LDAP groups or attributes
doBatchSyncGroups(ctx, ldapUsers, dnToIdentityKeyMap, errors);
//sync roles
doBatchSyncRoles(ctx, ldapUsers, dnToIdentityKeyMap, errors);
// update sync time and set running flag
lastSyncDate = timeBeforeSync;
ctx.close();
success = true;
log.audit("LDAP batch sync done: " + success + " in " + ((System.currentTimeMillis() - startTime) / 1000) + "s");
return success;
} catch (Exception e) {
errors.insert("Unknown error");
log.error("Error in LDAP batch sync, unknown reason", e);
success = false;
return success;
} finally {
WorkThreadInformations.unsetLongRunningTask("ldapSync");
freeSyncLock();
if(ctx != null) {
try {
ctx.close();
} catch (NamingException e) {
//try but failed silently
}
}
LDAPEvent endEvent = new LDAPEvent(LDAPEvent.SYNCHING_ENDED);
endEvent.setTimestamp(new Date());
endEvent.setSuccess(success);
endEvent.setErrors(errors);
coordinator.getEventBus().fireEventToListenersOf(endEvent, ldapSyncLockOres);
}
}
private void doBatchSyncRoles(LdapContext ctx, List<LDAPUser> ldapUsers, Map<String,LDAPUser> dnToIdentityKeyMap, LDAPError errors)
throws NamingException {
ctx.close();
ctx = bindSystem();
//authors
if(syncConfiguration.getAuthorsGroupBase() != null && syncConfiguration.getAuthorsGroupBase().size() > 0) {
List<LDAPGroup> authorGroups = ldapDao.searchGroups(ctx, syncConfiguration.getAuthorsGroupBase());
syncRole(ctx, authorGroups, Constants.GROUP_AUTHORS, dnToIdentityKeyMap, errors);
}
//user managers
if(syncConfiguration.getUserManagersGroupBase() != null && syncConfiguration.getUserManagersGroupBase().size() > 0) {
List<LDAPGroup> userManagerGroups = ldapDao.searchGroups(ctx, syncConfiguration.getUserManagersGroupBase());
syncRole(ctx, userManagerGroups, Constants.GROUP_USERMANAGERS, dnToIdentityKeyMap, errors);
}
//group managers
if(syncConfiguration.getGroupManagersGroupBase() != null && syncConfiguration.getGroupManagersGroupBase().size() > 0) {
List<LDAPGroup> groupManagerGroups = ldapDao.searchGroups(ctx, syncConfiguration.getGroupManagersGroupBase());
syncRole(ctx, groupManagerGroups, Constants.GROUP_GROUPMANAGERS, dnToIdentityKeyMap, errors);
}
//question pool managers
if(syncConfiguration.getQpoolManagersGroupBase() != null && syncConfiguration.getQpoolManagersGroupBase().size() > 0) {
List<LDAPGroup> qpoolManagerGroups = ldapDao.searchGroups(ctx, syncConfiguration.getQpoolManagersGroupBase());
syncRole(ctx, qpoolManagerGroups, Constants.GROUP_POOL_MANAGER, dnToIdentityKeyMap, errors);
}
//learning resource manager
if(syncConfiguration.getLearningResourceManagersGroupBase() != null && syncConfiguration.getLearningResourceManagersGroupBase().size() > 0) {
List<LDAPGroup> resourceManagerGroups = ldapDao.searchGroups(ctx, syncConfiguration.getLearningResourceManagersGroupBase());
syncRole(ctx, resourceManagerGroups, Constants.GROUP_INST_ORES_MANAGER, dnToIdentityKeyMap, errors);
}
int count = 0;
boolean syncAuthor = StringHelper.containsNonWhitespace(syncConfiguration.getAuthorRoleAttribute())
&& StringHelper.containsNonWhitespace(syncConfiguration.getAuthorRoleValue());
boolean syncUserManager = StringHelper.containsNonWhitespace(syncConfiguration.getUserManagerRoleAttribute())
&& StringHelper.containsNonWhitespace(syncConfiguration.getUserManagerRoleValue());
boolean syncGroupManager = StringHelper.containsNonWhitespace(syncConfiguration.getGroupManagerRoleAttribute())
&& StringHelper.containsNonWhitespace(syncConfiguration.getGroupManagerRoleValue());
boolean syncQpoolManager = StringHelper.containsNonWhitespace(syncConfiguration.getQpoolManagerRoleAttribute())
&& StringHelper.containsNonWhitespace(syncConfiguration.getQpoolManagerRoleValue());
boolean syncLearningResourceManager = StringHelper.containsNonWhitespace(syncConfiguration.getLearningResourceManagerRoleAttribute())
&& StringHelper.containsNonWhitespace(syncConfiguration.getLearningResourceManagerRoleValue());
for(LDAPUser ldapUser:ldapUsers) {
if(syncAuthor && ldapUser.isAuthor()) {
syncRole(ldapUser, Constants.GROUP_AUTHORS);
count++;
}
if(syncUserManager && ldapUser.isUserManager()) {
syncRole(ldapUser, Constants.GROUP_USERMANAGERS);
count++;
}
if(syncGroupManager && ldapUser.isGroupManager()) {
syncRole(ldapUser, Constants.GROUP_GROUPMANAGERS);
count++;
}
if(syncQpoolManager && ldapUser.isQpoolManager()) {
syncRole(ldapUser, Constants.GROUP_POOL_MANAGER);
count++;
}
if(syncLearningResourceManager && ldapUser.isLearningResourceManager()) {
syncRole(ldapUser, Constants.GROUP_INST_ORES_MANAGER);
count++;
}
if(count > 20) {
dbInstance.commitAndCloseSession();
count = 0;
}
}
dbInstance.commitAndCloseSession();
}
private void syncRole(LdapContext ctx, List<LDAPGroup> groups, String role,
Map<String,LDAPUser> dnToIdentityKeyMap, LDAPError errors) {
if(groups == null || groups.isEmpty()) return;
for(LDAPGroup group:groups) {
List<String> members = group.getMembers();
if(members != null && members.size() > 0) {
for(String member:members) {
LDAPUser ldapUser = getLDAPUser(ctx, member, dnToIdentityKeyMap, errors);
if(ldapUser != null && ldapUser.getCachedIdentity() != null) {
syncRole(ldapUser, role);
}
}
}
dbInstance.commitAndCloseSession();
}
}
private void syncRole(LDAPUser ldapUser, String role) {
IdentityRef identityRef = ldapUser.getCachedIdentity();
List<String> roleList = securityManager.getRolesAsString(identityRef);
if(!roleList.contains(role)) {
Identity identity = securityManager.loadIdentityByKey(identityRef.getKey());
Roles roles = securityManager.getRoles(identity);
switch(role) {
case Constants.GROUP_AUTHORS:
roles = new Roles(roles.isOLATAdmin(), roles.isUserManager(), roles.isGroupManager(), true,
false, roles.isInstitutionalResourceManager(), roles.isPoolAdmin(), false);
securityManager.updateRoles(null, identity, roles);
break;
case Constants.GROUP_USERMANAGERS:
roles = new Roles(roles.isOLATAdmin(), true, roles.isGroupManager(), roles.isAuthor(),
false, roles.isInstitutionalResourceManager(), roles.isPoolAdmin(), false);
securityManager.updateRoles(null, identity, roles);
break;
case Constants.GROUP_GROUPMANAGERS:
roles = new Roles(roles.isOLATAdmin(), roles.isUserManager(), true, roles.isAuthor(),
false, roles.isInstitutionalResourceManager(), roles.isPoolAdmin(), false);
securityManager.updateRoles(null, identity, roles);
break;
case Constants.GROUP_POOL_MANAGER:
roles = new Roles(roles.isOLATAdmin(), roles.isUserManager(), roles.isGroupManager(), roles.isAuthor(),
false, roles.isInstitutionalResourceManager(), true, false);
securityManager.updateRoles(null, identity, roles);
break;
case Constants.GROUP_INST_ORES_MANAGER:
roles = new Roles(roles.isOLATAdmin(), roles.isUserManager(), roles.isGroupManager(), roles.isAuthor(),
false, true, roles.isPoolAdmin(), false);
securityManager.updateRoles(null, identity, roles);
break;
}
}
}
private void doBatchSyncDeletedUsers(LdapContext ctx, String sinceSentence) {
// create User to Delete List
List<Identity> deletedUserList = getIdentitysDeletedInLdap(ctx);
// delete old users
if (deletedUserList == null || deletedUserList.size() == 0) {
log.info("LDAP batch sync: no users to delete" + sinceSentence);
} else {
if (ldapLoginModule.isDeleteRemovedLDAPUsersOnSync()) {
// check if more not more than the defined percentages of
// users managed in LDAP should be deleted
// if they are over the percentage, they will not be deleted
// by the sync job
SecurityGroup ldapGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
List<Identity> olatListIdentity = securityManager.getIdentitiesOfSecurityGroup(ldapGroup);
if (olatListIdentity.isEmpty())
log.info("No users managed by LDAP, can't delete users");
else {
int prozente = (int) (((float)deletedUserList.size() / (float) olatListIdentity.size())*100);
if (prozente >= ldapLoginModule.getDeleteRemovedLDAPUsersPercentage()) {
log.info("LDAP batch sync: more than "
+ ldapLoginModule.getDeleteRemovedLDAPUsersPercentage()
+ "% of LDAP managed users should be deleted. Please use Admin Deletion Job. Or increase deleteRemovedLDAPUsersPercentage. "
+ prozente
+ "% tried to delete.");
} else {
// delete users
deletIdentities(deletedUserList);
log.info("LDAP batch sync: "
+ deletedUserList.size() + " users deleted"
+ sinceSentence);
}
}
} else {
// Do nothing, only log users to logfile
StringBuilder users = new StringBuilder();
for (Identity toBeDeleted : deletedUserList) {
users.append(toBeDeleted.getName()).append(',');
}
log.info("LDAP batch sync: "
+ deletedUserList.size()
+ " users detected as to be deleted"
+ sinceSentence
+ ". Automatic deleting is disabled in LDAPLoginModule, delete these users manually::["
+ users.toString() + "]");
}
}
dbInstance.commitAndCloseSession();
}
private List<LDAPUser> doBatchSyncNewAndModifiedUsers(LdapContext ctx, String sinceSentence, Map<String,LDAPUser> dnToIdentityKeyMap, LDAPError errors) {
// Get new and modified users from LDAP
int count = 0;
List<LDAPUser> ldapUserList = ldapDao.getUserAttributesModifiedSince(lastSyncDate, ctx);
// Check for new and modified users
List<LDAPUser> newLdapUserList = new ArrayList<LDAPUser>();
Map<IdentityRef, Map<String, String>> changedMapIdentityMap = new HashMap<>();
for (LDAPUser ldapUser: ldapUserList) {
String user = null;
try {
Attributes userAttrs = ldapUser.getAttributes();
String uidProp = syncConfiguration.getOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
user = getAttributeValue(userAttrs.get(uidProp));
Identity identity = findIdentityByLdapAuthentication(userAttrs, errors);
if (identity != null) {
Map<String, String> changedAttrMap = prepareUserPropertyForSync(userAttrs, identity);
if (changedAttrMap != null) {
changedMapIdentityMap.put(identity, changedAttrMap);
}
if(StringHelper.containsNonWhitespace(ldapUser.getDn())) {
dnToIdentityKeyMap.put(ldapUser.getDn(), ldapUser);
ldapUser.setCachedIdentity(new IdentityRefImpl(identity.getKey()));
}
} else if (errors.isEmpty()) {
String[] reqAttrs = syncConfiguration.checkRequestAttributes(userAttrs);
if (reqAttrs == null) {
newLdapUserList.add(ldapUser);
} else {
log.warn("Error in LDAP batch sync: can't create user with username::" + user + " : missing required attributes::"
+ ArrayUtils.toString(reqAttrs), null);
}
} else {
log.warn(errors.get(), null);
}
} catch (Exception e) {
// catch here to go on with other users on exeptions!
log.error("some error occured in looping over set of changed user-attributes, actual user " + user + ". Will still continue with others.", e);
errors.insert("Cannot sync user: " + user);
} finally {
dbInstance.commit();
if(count % 10 == 0) {
dbInstance.closeSession();
}
}
if(count % 1000 == 0) {
log.info("Retrieve " + count + "/" + ldapUserList.size() + " users in LDAP server");
}
count++;
}
// sync existing users
if (changedMapIdentityMap == null || changedMapIdentityMap.isEmpty()) {
log.info("LDAP batch sync: no users to sync" + sinceSentence);
} else {
int syncCount = 0;
for (IdentityRef ident : changedMapIdentityMap.keySet()) {
// sync user is exception save, no try/catch needed
try {
syncCount++;
syncUser(changedMapIdentityMap.get(ident), ident);
} catch (Exception e) {
errors.insert("Cannot sync user: " + ident);
} finally {
dbInstance.commit();
if(syncCount % 20 == 0) {
dbInstance.closeSession();
}
}
if(syncCount % 1000 == 0) {
log.info("Update " + syncCount + "/" + changedMapIdentityMap.size() + " LDAP users");
}
}
log.info("LDAP batch sync: " + changedMapIdentityMap.size() + " users synced" + sinceSentence);
}
// create new users
if (newLdapUserList.isEmpty()) {
log.info("LDAP batch sync: no users to create" + sinceSentence);
} else {
int newCount = 0;
for (LDAPUser ldapUser: newLdapUserList) {
Attributes userAttrs = ldapUser.getAttributes();
try {
newCount++;
Identity identity = createAndPersistUser(userAttrs);
if(StringHelper.containsNonWhitespace(ldapUser.getDn())) {
dnToIdentityKeyMap.put(ldapUser.getDn(), ldapUser);
ldapUser.setCachedIdentity(new IdentityRefImpl(identity.getKey()));
}
} catch (Exception e) {
// catch here to go on with other users on exeptions!
log.error("some error occured while creating new users, actual userAttribs " + userAttrs + ". Will still continue with others.", e);
} finally {
dbInstance.commit();
if(newCount % 20 == 0) {
dbInstance.closeSession();
}
}
if(newCount % 1000 == 0) {
log.info("Create " + count + "/" + newLdapUserList.size() + " LDAP users");
}
}
log.info("LDAP batch sync: " + newLdapUserList.size() + " users created" + sinceSentence);
}
dbInstance.commitAndCloseSession();
return ldapUserList;
}
private void doBatchSyncGroups(LdapContext ctx, List<LDAPUser> ldapUsers, Map<String,LDAPUser> dnToIdentityKeyMap, LDAPError errors)
throws NamingException {
ctx.close();
log.info("LDAP batch sync LDAP user to OO groups");
ctx = bindSystem();
//sync groups by LDAP groups or attributes
Map<String,LDAPGroup> cnToGroupMap = new HashMap<>();
// retrieve all ldap group's with their list of members
if(syncConfiguration.syncGroupWithLDAPGroup()) {
List<String> groupDNs = syncConfiguration.getLdapGroupBases();
List<LDAPGroup> ldapGroups = ldapDao.searchGroups(ctx, groupDNs);
for(LDAPGroup ldapGroup:ldapGroups) {
cnToGroupMap.put(ldapGroup.getCommonName(), ldapGroup);
}
}
if(syncConfiguration.syncGroupWithAttribute()) {
doSyncGroupByAttribute(ldapUsers, cnToGroupMap);
}
for(LDAPGroup group:cnToGroupMap.values()) {
BusinessGroup managedGroup = getManagerBusinessGroup(group.getCommonName());
if(managedGroup != null) {
syncBusinessGroup(ctx, managedGroup, group, dnToIdentityKeyMap, errors);
}
dbInstance.commitAndCloseSession();
}
}
private void doSyncGroupByAttribute(List<LDAPUser> ldapUsers, Map<String,LDAPGroup> cnToGroupMap) {
for(LDAPUser ldapUser:ldapUsers) {
List<String> groupIds = ldapUser.getGroupIds();
List<String> coachedGroupIds = ldapUser.getCoachedGroupIds();
if((groupIds != null && groupIds.size() > 0) || (coachedGroupIds != null && coachedGroupIds.size() > 0)) {
IdentityRef identity = ldapUser.getCachedIdentity();
if(identity == null) {
log.error("Identity with dn=" + ldapUser.getDn() + " not found");
} else {
if(groupIds != null && groupIds.size() > 0) {
for(String groupId:groupIds) {
if(!cnToGroupMap.containsKey(groupId)) {
cnToGroupMap.put(groupId, new LDAPGroup(groupId));
}
cnToGroupMap.get(groupId).getParticipants().add(ldapUser);
}
}
if(coachedGroupIds != null && coachedGroupIds.size() > 0) {
for(String coachedGroupId:coachedGroupIds) {
if(!cnToGroupMap.containsKey(coachedGroupId)) {
cnToGroupMap.put(coachedGroupId, new LDAPGroup(coachedGroupId));
}
cnToGroupMap.get(coachedGroupId).getCoaches().add(ldapUser);
}
}
}
}
}
}
private void syncBusinessGroup(LdapContext ctx, BusinessGroup businessGroup, LDAPGroup ldapGroup, Map<String,LDAPUser> dnToIdentityKeyMap, LDAPError errors) {
List<Identity> currentMembers = businessGroupRelationDao
.getMembers(businessGroup, GroupRoles.coach.name(), GroupRoles.participant.name());
List<LDAPUser> coaches = new ArrayList<>(ldapGroup.getCoaches());
List<LDAPUser> participants = new ArrayList<>(ldapGroup.getParticipants());
// transfer member cn's to the participants list
for(String member:ldapGroup.getMembers()) {
LDAPUser ldapUser = getLDAPUser(ctx, member, dnToIdentityKeyMap, errors); dnToIdentityKeyMap.get(member);
if(ldapUser != null && !participants.contains(ldapUser)) {
participants.add(ldapUser);
}
}
// transfer to ldap user flagged as coach to the coach list
for(Iterator<LDAPUser> participantIt=participants.iterator(); participantIt.hasNext(); ) {
LDAPUser participant = participantIt.next();
if(participant.isCoach()) {
if(!coaches.contains(participant)) {
coaches.add(participant);
}
participantIt.remove();
}
}
int count = 0;
for(LDAPUser participant:participants) {
IdentityRef memberIdentity = participant.getCachedIdentity();
syncMembership(businessGroup, memberIdentity, false);
currentMembers.remove(memberIdentity);
if(count % 20 == 0) {
dbInstance.commitAndCloseSession();
}
}
for(LDAPUser coach:coaches) {
IdentityRef memberIdentity = coach.getCachedIdentity();
syncMembership(businessGroup, memberIdentity, true);
currentMembers.remove(memberIdentity);
if(count % 20 == 0) {
dbInstance.commitAndCloseSession();
}
}
for(Identity currentMember:currentMembers) {
List<String> roles = businessGroupRelationDao.getRoles(currentMember, businessGroup);
for(String role:roles) {
businessGroupRelationDao.removeRole(currentMember, businessGroup, role);
}
if(count % 20 == 0) {
dbInstance.commitAndCloseSession();
}
}
}
private void syncMembership(BusinessGroup businessGroup, IdentityRef identityRef, boolean coach) {
if(identityRef != null) {
List<String> roles = businessGroupRelationDao.getRoles(identityRef, businessGroup);
if(roles.isEmpty()) {
Identity identity = securityManager.loadIdentityByKey(identityRef.getKey());
if(coach) {
businessGroupRelationDao.addRole(identity, businessGroup, GroupRoles.coach.name());
} else {
businessGroupRelationDao.addRole(identity, businessGroup, GroupRoles.participant.name());
}
} else if(coach && roles.size() == 1 && roles.contains(GroupRoles.coach.name())) {
//coach and only coach, do nothing
} else if(!coach && roles.size() == 1 && roles.contains(GroupRoles.participant.name())) {
//participant and only participant, do nothing
} else {
boolean already = false;
Identity identity = securityManager.loadIdentityByKey(identityRef.getKey());
String mainRole = coach ? GroupRoles.coach.name() : GroupRoles.participant.name();
for(String role:roles) {
if(mainRole.equals(role)) {
already = true;
} else {
businessGroupRelationDao.removeRole(identity, businessGroup, role);
}
}
if(!already) {
businessGroupRelationDao.addRole(identity, businessGroup, mainRole);
}
}
}
}
private BusinessGroup getManagerBusinessGroup(String externalId) {
SearchBusinessGroupParams params = new SearchBusinessGroupParams();
params.setExternalId(externalId);
List<BusinessGroup> businessGroups = businessGroupService.findBusinessGroups(params, null, 0, -1);
BusinessGroup managedBusinessGroup;
if(businessGroups.size() == 0) {
String managedFlags = BusinessGroupManagedFlag.membersmanagement.name() + "," + BusinessGroupManagedFlag.delete.name();
managedBusinessGroup = businessGroupService
.createBusinessGroup(null, externalId, externalId, externalId, managedFlags, null, null, false, false, null);
} else if(businessGroups.size() == 1) {
managedBusinessGroup = businessGroups.get(0);
} else {
log.error(businessGroups.size() + " managed groups found with the following external id: " + externalId);
managedBusinessGroup = null;
}
return managedBusinessGroup;
}
private LDAPUser getLDAPUser(LdapContext ctx, String member, Map<String,LDAPUser> dnToIdentityKeyMap, LDAPError errors) {
LDAPUser ldapUser = dnToIdentityKeyMap.get(member);
IdentityRef identity = ldapUser == null ? null : ldapUser.getCachedIdentity();
if(identity == null) {
String userFilter = syncConfiguration.getLdapUserFilter();
String userDN = member;
LDAPUserVisitor visitor = new LDAPUserVisitor(syncConfiguration);
ldapDao.search(visitor, userDN, userFilter, syncConfiguration.getUserAttributes(), ctx);
List<LDAPUser> ldapUserList = visitor.getLdapUserList();
if(ldapUserList.size() == 1) {
ldapUser = ldapUserList.get(0);
Attributes userAttrs = ldapUser.getAttributes();
identity = findIdentityByLdapAuthentication(userAttrs, errors);
if(identity != null) {
dnToIdentityKeyMap.put(userDN, ldapUser);
}
}
}
return ldapUser;
}
@Override
public void doSyncSingleUser(Identity ident){
LdapContext ctx = bindSystem();
if (ctx == null) {
log.error("could not bind to ldap", null);
}
String userDN = ldapDao.searchUserDNByUid(ident.getName(), ctx);
final List<Attributes> ldapUserList = new ArrayList<Attributes>();
// TODO: use userDN instead of filter to get users attribs
ldapDao.searchInLdap(new LDAPVisitor() {
@Override
public void visit(SearchResult result) {
Attributes resAttribs = result.getAttributes();
log.debug(" found : " + resAttribs.size() + " attributes in result " + result.getName());
ldapUserList.add(resAttribs);
}
}, userDN, syncConfiguration.getUserAttributes(), ctx);
Attributes attrs = ldapUserList.get(0);
Map<String, String> olatProToSync = prepareUserPropertyForSync(attrs, ident);
if (olatProToSync != null) {
syncUser(olatProToSync, ident);
}
}
@Override
public void doSyncSingleUserWithLoginAttribute(Identity ident) {
LdapContext ctx = bindSystem();
if (ctx == null) {
log.error("could not bind to ldap", null);
}
String ldapUserIDAttribute = syncConfiguration.getLdapUserLoginAttribute();
String filter = ldapDao.buildSearchUserFilter(ldapUserIDAttribute, ident.getName());
List<Attributes> ldapUserAttrs = new ArrayList<>();
ldapDao.searchInLdap(new LDAPVisitor() {
@Override
public void visit(SearchResult result) {
ldapUserAttrs.add(result.getAttributes());
}
}, filter, syncConfiguration.getUserAttributes(), ctx);
if(ldapUserAttrs.size() == 1) {
Attributes attrs = ldapUserAttrs.get(0);
Map<String, String> olatProToSync = prepareUserPropertyForSync(attrs, ident);
if (olatProToSync != null) {
syncUser(olatProToSync, ident);
}
} else {
log.error("Cannot sync the user because it was not found on LDAP server: " + ident);
}
}
/**
* @see org.olat.ldap.LDAPLoginManager#getLastSyncDate()
*/
@Override
public Date getLastSyncDate() {
return lastSyncDate;
}
/**
* Internal helper to add the SSL protocol to the environment
*
* @param env
*/
private void enableSSL(Hashtable<String, String> env) {
env.put(Context.SECURITY_PROTOCOL, "ssl");
if(StringHelper.containsNonWhitespace(ldapLoginModule.getTrustStoreLocation())) {
System.setProperty("javax.net.ssl.trustStore", ldapLoginModule.getTrustStoreLocation());
}
}
/**
* Acquire lock for administration jobs
*
*/
@Override
public synchronized boolean acquireSyncLock(){
if(batchSyncIsRunning){
return false;
}
batchSyncIsRunning=true;
return true;
}
/**
* Release lock for administration jobs
*
*/
@Override
public synchronized void freeSyncLock() {
batchSyncIsRunning = false;
}
/**
* remove all cached authentications for fallback-login. useful if users logged in first with a default pw and changed it outside in AD/LDAP, but OLAT doesn't know about.
* removing fallback-auths means login is only possible by AD/LDAP and if server is reachable!
* see FXOLAT-284
*/
@Override
public void removeFallBackAuthentications() {
if (ldapLoginModule.isCacheLDAPPwdAsOLATPwdOnLogin()){
SecurityGroup ldapGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
if (ldapGroup == null) {
log.error("Error getting user from OLAT security group '" + LDAPConstants.SECURITY_GROUP_LDAP + "' : group does not exist", null);
}
List<Identity> ldapIdents = securityManager.getIdentitiesOfSecurityGroup(ldapGroup);
log.info("found " + ldapIdents.size() + " identies in ldap security group");
int count=0;
for (Identity identity : ldapIdents) {
Authentication auth = securityManager.findAuthentication(identity, BaseSecurityModule.getDefaultAuthProviderIdentifier());
if (auth!=null){
securityManager.deleteAuthentication(auth);
count++;
}
if (count % 20 == 0){
dbInstance.intermediateCommit();
}
}
log.info("removed cached authentications (fallback login provider: " + BaseSecurityModule.getDefaultAuthProviderIdentifier() + ") for " + count + " users.");
}
}
@Override
public boolean isIdentityInLDAPSecGroup(Identity ident) {
SecurityGroup ldapSecurityGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
return ldapSecurityGroup != null && securityManager.isIdentityInSecurityGroup(ident, ldapSecurityGroup);
}
}