package org.olat.ldap;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.Map.Entry;
import javax.naming.AuthenticationException;
import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.SizeLimitExceededException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import net.fortuna.ical4j.util.TimeZones;
import org.apache.commons.lang.ArrayUtils;
import org.olat.admin.user.delete.service.UserDeletionManager;
import org.olat.basesecurity.Authentication;
import org.olat.basesecurity.Constants;
import org.olat.basesecurity.Manager;
import org.olat.basesecurity.ManagerFactory;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.commons.persistence.DBFactory;
import org.olat.core.id.Identity;
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.ldap.ui.LDAPAuthenticationController;
import org.olat.user.UserManager;
/**
* Description: This manager handles communication between LDAP and OLAT. LDAP access is done by JNDI.
* <p>
* LDAPLoginMangerImpl
* <ul>
* </ul>
* <p>
*
* @author Maurus Rohrer
*/
public class LDAPLoginManagerImpl extends LDAPLoginManager {
private static OLog log = Tracing.createLoggerFor(LDAPLoginManagerImpl.class);
private static final TimeZone UTC_TIME_ZONE;
private static final SimpleDateFormat generalizedTimeFormatter;
private static boolean batchSyncIsRunning = false;
private static Date lastSyncDate = null; // first sync is always a full sync
static {
UTC_TIME_ZONE = TimeZone.getTimeZone(TimeZones.UTC_ID);
generalizedTimeFormatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
generalizedTimeFormatter.setTimeZone(UTC_TIME_ZONE);
}
/**
* Private constructor. Use LDAPLoginManager.getInstance() method instead
*/
private LDAPLoginManagerImpl() {
super();
}
/**
* Connect to the LDAP server with System DN and Password
*
* Configuration: LDAP URL = olatextconfig.xml (property=ldapURL) System DN =
* olatextconfig.xml (property=ldapSystemDN) System PW = olatextconfig.xml
* (property=ldapSystemPW)
*
* @return The LDAP connection (InitialDirContext) or NULL if connect fails
*
* @throws NamingException
*/
public InitialDirContext 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());
// check ssl
if (LDAPLoginModule.isSslEnabled()) {
enableSSL(env);
}
try {
InitialDirContext ctx = new InitialDirContext(env);
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;
}
}
/**
*
* Connect to LDAP with the User-Name and Password given as parameters
*
* Configuration: LDAP URL = olatextconfig.xml (property=ldapURL) LDAP Base =
* olatextconfig.xml (property=ldapBase) LDAP Attributes Map =
* olatextconfig.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 succsessful bind Attributes otherwise NULL
*
* @throws NamingException
*/
public Attributes bindUser(String uid, String pwd, LDAPError errors) {
// get user name, password and attributes
String ldapUrl = LDAPLoginModule.getLdapUrl();
List<String> ldapBases = LDAPLoginModule.getLdapBases();
String objctClass = LDAPLoginModule.getLdapUserObjectClass();
String[] userAttr = LDAPLoginModule.getUserAttrs();
String[] serachAttr = { "dn" };
InitialDirContext ctx;
if (uid == null || pwd == null) {
if (log.isDebug()) log.debug("Error when trying to bind user, missing username or password. Username::" + uid + " pwd::" + pwd);
errors.insert("Username and passwort must be selected");
return null;
}
String ldapUserIDAttribute = LDAPHelper.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
String filter = "(&(objectClass=" + objctClass + ")(" + ldapUserIDAttribute + "=" + uid + "))";
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(serachAttr);
ctx = bindSystem();
if (ctx == null) {
errors.insert("LDAP connection error");
return null;
}
String userDN = 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 (userDN != null) break;
} catch (NamingException e) {
log.error("NamingException when trying to bind user with username::" + uid + " on ldapBase::" + ldapBase, e);
}
}
if (userDN == null) {
log.info("Error when trying to bind user with username::" + uid + " - user not found on LDAP server"
+ (LDAPLoginModule.isCacheLDAPPwdAsOLATPwdOnLogin() ? ", trying with OLAT login provider" : ""));
errors.insert("Username or passwort 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.isSslEnabled()) {
enableSSL(env);
}
try {
InitialDirContext userBind = new InitialDirContext(env);
Attributes attributes = userBind.getAttributes(userDN, userAttr);
userBind.close();
return attributes;
} catch (AuthenticationException e) {
log.info("Error when trying to bind user with username::" + uid + " - invalid LDAP password");
errors.insert("Username or passwort incorrect");
return null;
} catch (NamingException e) {
log.error("NamingException when trying to get attributes after binding user with username::" + uid, e);
errors.insert("Username or passwort incorrect");
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 void changePassword(Identity identity, String pwd, LDAPError errors) {
String uid = identity.getName();
String ldapUserPasswordAttribute = LDAPLoginModule.getLdapUserPasswordAttribute();
try {
DirContext ctx = bindSystem();
String dn = searchUserDN(uid, ctx);
ModificationItem [] modificationItems = new ModificationItem [ 1 ];
Attribute userPasswordAttribute;
if(LDAPLoginModule.isActiveDirectory()) {
//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);
}
userPasswordAttribute = new BasicAttribute ( ldapUserPasswordAttribute, pwdArray );
} else {
userPasswordAttribute = new BasicAttribute ( ldapUserPasswordAttribute, pwd );
}
modificationItems [ 0 ] = new ModificationItem ( DirContext.REPLACE_ATTRIBUTE, userPasswordAttribute );
ctx.modifyAttributes ( dn, modificationItems );
ctx.close();
} catch (NamingException e) {
log.error("NamingException when trying to change password with username::" + uid, e);
errors.insert("Cannot change the password");
}
}
/**
* Find the user dn with its uid
* @param uid
* @param ctx
* @return user's dn
*/
private String searchUserDN(String uid, DirContext ctx) {
if(ctx == null)
return null;
List<String> ldapBases = LDAPLoginModule.getLdapBases();
String objctClass = LDAPLoginModule.getLdapUserObjectClass();
String[] serachAttr = { "dn" };
String ldapUserIDAttribute = LDAPHelper.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
String filter = "(&(objectClass=" + objctClass + ")(" + ldapUserIDAttribute + "=" + uid + "))";
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(serachAttr);
String userDN = 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 (userDN != null) {
break;
}
} catch (NamingException e) {
log.error("NamingException when trying to bind user with username::" + uid + " on ldapBase::" + ldapBase, e);
}
}
return userDN;
}
/**
*
* Creates list of all LDAP Users or changed Users since syncTime
*
* Configuration: userAttr = olatextconfig.xml (property=userAttrs) LDAP Base =
* olatextconfig.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 Arguments of found users or empty list if search
* fails or nothing is changed
*
* @throws NamingException
*/
public List<Attributes> getUserAttributesModifiedSince(Date syncTime, InitialDirContext ctx) {
String[] userAttr = LDAPLoginModule.getUserAttrs();
List<String> ldapBases = LDAPLoginModule.getLdapBases();
String objctClass = LDAPLoginModule.getLdapUserObjectClass();
StringBuffer filterBuffer = new StringBuffer();
if (syncTime == null) {
filterBuffer.append("(objectClass=").append(objctClass).append(")");
} else {
String syncTimeForm = generalizedTimeFormatter.format(syncTime);
filterBuffer.append("(&(objectClass=").append(objctClass).append(")(|(");
filterBuffer.append(LDAPLoginModule.getLdapUserLastModifiedTimestampAttribute()).append(">=").append(syncTimeForm);
filterBuffer.append(")(");
filterBuffer.append(LDAPLoginModule.getLdapUserCreatedTimestampAttribute()).append(">=").append(syncTimeForm);
filterBuffer.append(")))");
}
String filter = filterBuffer.toString();
List<Attributes> ldapUserList = new LinkedList<Attributes>();
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(userAttr);
ctls.setCountLimit(0); // no limits
int counter = 0;
try {
for (String ldapBase : ldapBases) {
NamingEnumeration<SearchResult> enm = ctx.search(ldapBase, filter, ctls);
while (enm.hasMore()) {
SearchResult result = enm.next();
ldapUserList.add(result.getAttributes());
counter++;
}
}
} catch (SizeLimitExceededException e) {
log
.error("SizeLimitExceededException after "
+ counter
+ " records when getting all user attributers from LDAP, reconfigure your LDAP server, hints: http://www.ldapbrowser.com/forum/viewtopic.php?t=14");
return null;
} catch (NamingException e) {
log.error("NamingException when trying to get modified LDAP user attributes with filter::" + filter + " at row::" + counter, e);
}
return ldapUserList;
}
/**
* Delete all Identities in List and removes them from LDAPSecurityGroup
*
* @param identityList List of Identities to delete
*/
public void deletIdentities(List<Identity> identityList) {
Manager securityManager = ManagerFactory.getManager();
Identity identity;
UserDeletionManager deletionManager = UserDeletionManager.getInstance();
SecurityGroup secGroup = securityManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
for (Iterator<Identity> itr = identityList.iterator(); itr.hasNext();) {
identity = itr.next();
securityManager.removeIdentityFromSecurityGroup(identity, secGroup);
deletionManager.deleteIdentity(identity);
}
}
/**
* Sync all OLATPropertys in Map of Identity
*
* @param olatPropertyMap Map of changed OLAT properties
* (OLATProperty,LDAPValue)
* @param identity Identity to sync
*/
public void syncUser(Map<String, String> olatPropertyMap, Identity identity) {
if (identity == null) {
log.warn("Identiy is null - should not happen");
return;
}
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 = LDAPLoginModule.getSyncOnlyOnCreateProperties();
if (syncOnlyOnCreateProperties != null) {
for (String syncOnlyOnCreateKey : syncOnlyOnCreateProperties) {
olatPropertyMap.remove(syncOnlyOnCreateKey);
}
}
Iterator<String> itrSync = olatPropertyMap.keySet().iterator();
while (itrSync.hasNext()) {
String key = itrSync.next();
user.setProperty(key, olatPropertyMap.get(key));
}
// Add static user properties from the configuration
Map<String, String> staticProperties = LDAPLoginModule.getStaticUserProperties();
if (staticProperties != null && staticProperties.size() > 0) {
for (Entry<String, String> staticProperty : staticProperties.entrySet()) {
user.setProperty(staticProperty.getKey(), staticProperty.getValue());
}
}
}
/**
* 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
*/
@SuppressWarnings("unchecked")
public void createAndPersistUser(Attributes userAttributes) {
// Get and Check Config
String[] reqAttrs = LDAPHelper.checkReqAttr(userAttributes);
if (reqAttrs != null) {
log.warn("Can not create and persist user, the following attributes are missing::" + ArrayUtils.toString(reqAttrs));
return;
}
Manager securityManager = ManagerFactory.getManager();
String uid = LDAPHelper.getAttributeValue(userAttributes.get(LDAPHelper
.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER)));
String email = LDAPHelper.getAttributeValue(userAttributes.get(LDAPHelper.mapOlatPropertyToLdapAttribute(UserConstants.EMAIL)));
// Lookup user
if (securityManager.findIdentityByName(uid) != null) {
log.error("Can't create user with username='" + uid + "', does already exist in OLAT database");
return;
}
// Create User (first and lastname is added in next step)
User user = UserManager.getInstance().createUser(null, null, email);
// Set User Property's (Iterates over Attributes and gets OLAT Property out
// of olatexconfig.xml)
NamingEnumeration<Attribute> neAttr = (NamingEnumeration<Attribute>) userAttributes.getAll();
try {
while (neAttr.hasMore()) {
Attribute attr = neAttr.next();
String olatProperty = LDAPHelper.mapLdapAttributeToOlatProperty(attr.getID());
if (attr.get() != uid) {
String ldapValue = LDAPHelper.getAttributeValue(attr);
if (olatProperty == null || ldapValue == null) continue;
user.setProperty(olatProperty, ldapValue);
}
}
// Add static user properties from the configuration
Map<String, String> staticProperties = LDAPLoginModule.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;
} 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;
}
// Create Identity
Identity identity = securityManager.createAndPersistIdentityAndUser(uid, user, LDAPAuthenticationController.PROVIDER_LDAP, uid, null);
// 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);
}
/**
* 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 = LDAPHelper.mapLdapAttributeToOlatProperty(attr.getID());
if(olatProperty == null) {
continue;
}
String ldapValue = LDAPHelper.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) return null;
return olatPropertyMap;
} catch (NamingException e) {
log.error("NamingException when trying to prepare user properties for LDAP sync", e);
return null;
}
}
/**
* 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)
*/
public Identity findIdentyByLdapAuthentication(String uid, LDAPError errors) {
Manager securityManager = ManagerFactory.getManager();
Identity identity = securityManager.findIdentityByName(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");
return null;
}
if (securityManager.isIdentityInSecurityGroup(identity, ldapGroup)) {
Authentication ldapAuth = ManagerFactory.getManager().findAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP);
if(ldapAuth == null) {
//BUG Fixe: update the user and test if it has a ldap provider
ManagerFactory.getManager().createAndPersistAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP, identity.getName(), null);
}
return identity;
}
else {
if (LDAPLoginModule.isConvertExistingLocalUsersToLDAPUsers()) {
// Add user to LDAP security group and add the ldap provider
ManagerFactory.getManager().createAndPersistAuthentication(identity, LDAPAuthenticationController.PROVIDER_LDAP, identity.getName(), 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 = olatextconfig.xml (property=reqAttrs)
* LDAP Base = olatextconfig.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(InitialDirContext ctx) {
String userID = LDAPHelper.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER);
String objctClass = LDAPLoginModule.getLdapUserObjectClass();
List<String> ldapBases = LDAPLoginModule.getLdapBases();
List<String> ldapList = new LinkedList<String>();
List<Identity> identityListToDelete = new LinkedList<Identity>();
if (ctx == null) return null;
// Find all LDAP Users
SearchControls ctls = new SearchControls();
ctls.setSearchScope(SearchControls.SUBTREE_SCOPE);
ctls.setReturningAttributes(new String[] { userID });
ctls.setCountLimit(0); // set no limits
for (String ldapBase : ldapBases) {
int counter = 0;
try {
NamingEnumeration<SearchResult> enm = ctx.search(ldapBase, "(objectClass=" + objctClass + ")", ctls);
while (enm.hasMore()) {
SearchResult result = enm.next();
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());
}
counter++;
}
} catch (SizeLimitExceededException e) {
log
.error("SizeLimitExceededException after "
+ counter
+ " records when getting all users from LDAP, reconfigure your LDAP server, hints: http://www.ldapbrowser.com/forum/viewtopic.php?t=14");
return null;
} catch (NamingException e) {
log.error("NamingException when trying to fetch deleted users from LDAP using ldapBase::" + ldapBase + " on row::" + counter, e);
return null;
}
}
if (ldapList.size() == 0) {
log.warn("No users in LDAP found, can't create deletionList!!");
return null;
}
// Find all User in OLAT, members of LDAPSecurityGroup
Manager securityManager = ManagerFactory.getManager();
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");
return null;
}
List<Identity> olatListIdentity = securityManager.getIdentitiesOfSecurityGroup(ldapGroup);
ListIterator<Identity> itr = olatListIdentity.listIterator();
while (itr.hasNext()) {
Identity ida = itr.next();
// 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 olatextconfig.xml
*
* @param LDAPError
*
*/
public boolean doBatchSync(LDAPError errors) {
// 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;
}
}
try {
acquireSyncLock();
InitialDirContext ctx;
List<Attributes> ldapUserList;
List<Attributes> newLdapUserList;
Map<Identity, Map<String, String>> changedMapIdenityMap;
List<Identity> deletedUserList;
String user;
LDAPLoginManager ldapMan = LDAPLoginManager.getInstance();
ctx = ldapMan.bindSystem();
if (ctx == null) {
errors.insert("LDAP connection ERROR");
log.error("Error in LDAP batch sync: LDAP connection empty");
freeSyncLock();
return false;
}
// Get time before sync to have a save sync time when sync is successful
Date timeBeforeSync = new Date();
String sinceSentence = (lastSyncDate == null ? " (full sync)" : " since last sync from " + lastSyncDate);
// create User to Delete List
deletedUserList = ldapMan.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
Manager securityManager = ManagerFactory.getManager();
SecurityGroup ldapGroup = securityManager
.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
List<Identity> olatListIdentity = securityManager
.getIdentitiesOfSecurityGroup(ldapGroup);
if (olatListIdentity.size() == 0)
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
ldapMan.deletIdentities(deletedUserList);
log.info("LDAP batch sync: "
+ deletedUserList.size() + " users deleted"
+ sinceSentence);
}
}
} else {
// Do nothing, only log users to logfile
StringBuffer users = new StringBuffer();
for (Iterator iterator = deletedUserList.iterator(); iterator
.hasNext();) {
Identity toBeDeleted = (Identity) iterator.next();
users.append(toBeDeleted.getName());
if (iterator.hasNext())
users.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() + "]");
}
}
// Get new and modified users from LDAP
ldapUserList = ldapMan.getUserAttributesModifiedSince(lastSyncDate, ctx);
Identity identity;
// Check for new and modified users
Map<String, String> changedAttrMap;
newLdapUserList = new LinkedList<Attributes>();
changedMapIdenityMap = new HashMap<Identity, Map<String, String>>();
for (int i = 0; i < ldapUserList.size(); i++) {
user = LDAPHelper.getAttributeValue(ldapUserList.get(i).get(
LDAPHelper.mapOlatPropertyToLdapAttribute(LDAPConstants.LDAP_USER_IDENTIFYER)));
identity = ldapMan.findIdentyByLdapAuthentication(user, errors);
if (identity != null) {
changedAttrMap = ldapMan.prepareUserPropertyForSync(ldapUserList.get(i), identity);
if (changedAttrMap != null) changedMapIdenityMap.put(identity, changedAttrMap);
} else {
if (errors.isEmpty()) {
String[] reqAttrs = LDAPHelper.checkReqAttr(ldapUserList.get(i));
if (reqAttrs == null) newLdapUserList.add(ldapUserList.get(i));
else log.warn("Error in LDAP batch sync: can't create user with username::" + user + " : missing required attributes::"
+ ArrayUtils.toString(reqAttrs));
} else {
log.warn(errors.get());
}
}
}
// sync existing users
if (changedMapIdenityMap == null || changedMapIdenityMap.size() == 0) {
log.info("LDAP batch sync: no users to sync" + sinceSentence);
} else {
Iterator<Identity> itrIdent = changedMapIdenityMap.keySet().iterator();
while (itrIdent.hasNext()) {
Identity ident = itrIdent.next();
ldapMan.syncUser(changedMapIdenityMap.get(ident), ident);
}
log.info("LDAP batch sync: " + changedMapIdenityMap.size() + " users synced" + sinceSentence);
}
// create new users
if (newLdapUserList.size() == 0) {
log.info("LDAP batch sync: no users to create" + sinceSentence);
} else {
for (int i = 0; i < newLdapUserList.size(); i++) {
ldapMan.createAndPersistUser(newLdapUserList.get(i));
// commit new users after 100 users
DBFactory.getInstance().intermediateCommit();
}
log.info("LDAP batch sync: " + newLdapUserList.size() + " users created" + sinceSentence);
}
// update sync time and set running flag
lastSyncDate = timeBeforeSync;
freeSyncLock();
ctx.close();
return true;
} catch (Exception e) {
// don't block following retries
freeSyncLock();
errors.insert("Unknown error");
log.error("Error in LDAP batch sync, unknown reason", e);
return false;
}
}
/**
* @see org.olat.ldap.LDAPLoginManager#getLastSyncDate()
*/
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");
System.setProperty("javax.net.ssl.trustStore", LDAPLoginModule.getTrustStoreLocation());
}
/**
* Acquire lock for administration jobs
*
*/
public synchronized boolean acquireSyncLock(){
if(batchSyncIsRunning){
return false;
}
batchSyncIsRunning=true;
return true;
}
/**
* Release lock for administration jobs
*
*/
public synchronized void freeSyncLock() {
batchSyncIsRunning=false;
}
}