package org.olat.ldap;
import java.text.ParseException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;
import org.olat.basesecurity.Manager;
import org.olat.basesecurity.ManagerFactory;
import org.olat.basesecurity.SecurityGroup;
import org.olat.core.CoreSpringFactory;
import org.olat.core.configuration.OLATModule;
import org.olat.core.extensions.ExtManager;
import org.olat.core.logging.OLog;
import org.olat.core.logging.StartupException;
import org.olat.core.logging.Tracing;
import org.olat.core.util.StringHelper;
import org.olat.ldap.ui.LDAPAdminExtension;
import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import com.anthonyeden.lib.config.Configuration;
/**
* Description:
* This Module loads all needed configuration for the LDAP Login.
* All configuration is done in the spring olatextconfig.xml file.
* <p>
* LDAPLoginModule
* <p>
*
* @author maurus.rohrer@gmail.com
*/
public class LDAPLoginModule implements OLATModule {
// Connection configuration
private static String ldapUrl;
private static boolean ldapEnabled;
private static boolean activeDirectory;
//SSL configuration
private static boolean sslEnabled;
private static String trustStoreLoc;
private static String trustStorePass;
private static String trustStoreTyp;
// System user: used for getting all users and connection testing
private static String systemDN;
private static String systemPW;
// List of bases where to find users
private static List<String> ldapBases;
// Use a valid ldap password and save it as olat password to reduce dependency
// to LDAP server availability and allow WeDAV access
private static boolean cacheLDAPPwdAsOLATPwdOnLogin;
// When the system detects an LDAP user that does already exist in OLAT but is not marked
// as LDAP user, the OLAT user can be converted to an LDAP managed user.
// When enabling this feature you should make sure that you don't have a user 'administrator'
// in your ldapBases (not a problem but not recommended)
private static boolean convertExistingLocalUsersToLDAPUsers;
// Users that have been created vial LDAP sync but now can't be found on the LDAP anymore
// can be deleted automatically. If unsure, set to false and delete those users manually
// in the user management.
private static boolean deleteRemovedLDAPUsersOnSync;
// LDAP sync will not delete users if more than deleteRemovedLDAPUserPercentage are found to be deleted.
private static int deleteRemovedLDAPUsersPercentage;
// Propagate the password changes onto the LDAP server
private static boolean propagatePasswordChangedOnLdapServer;
// Configuration for syncing user attributes
private static String ldapUserObjectClass;
private static String ldapUserCreatedTimestampAttribute;
private static String ldapUserLastModifiedTimestampAttribute;
private static String ldapUserPasswordAttribute;
// Should users be created and synchronized automatically? If you set this
// configuration to false, the users will be generated on-the-fly when they
// log in
private static boolean ldapSyncOnStartup;
private static boolean ldapSyncCronSync;
private static String ldapSyncCronSyncExpression;
// User LDAP attributes to be synced and a map with the mandatory attributes
private static Map<String, String> userAttrMap;
private static Map<String, String> reqAttr;
private static Set<String> syncOnlyOnCreateProperties;
private static String[] userAttr;
// Static user properties that should be added to user when syncing
private static Map<String, String> staticUserProperties;
private static OLog log = Tracing.createLoggerFor(LDAPLoginModule.class);
/**
* @see org.olat.core.configuration.OLATModule#init(com.anthonyeden.lib.config.Configuration)
*/
public void init(Configuration moduleConfig) {
// Check if LDAP is enabled
if (!isLDAPEnabled()) {
log.info("LDAP login is disabled");
return;
}
// Create LDAP Security Group if not existing. Used to identify users that
// have to be synced with LDAP
Manager scrManager = ManagerFactory.getManager();
LDAPLoginManager ldapManager = LDAPLoginManager.getInstance();
SecurityGroup ldapGroup = scrManager.findSecurityGroupByName(LDAPConstants.SECURITY_GROUP_LDAP);
if (ldapGroup == null) {
ldapGroup = scrManager.createAndPersistNamedSecurityGroup(LDAPConstants.SECURITY_GROUP_LDAP);
}
// check for valid configuration
if (!checkConfigParameterIsNotEmpty(ldapUrl)) return;
if (!checkConfigParameterIsNotEmpty(systemDN)) return;
if (!checkConfigParameterIsNotEmpty(systemPW)) return;
if (ldapBases == null || ldapBases.size() == 0) {
log
.error("Missing configuration 'ldapBases'. Add at least one LDAP Base to the this configuration in olatextconfig.xml first. Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
if (!checkConfigParameterIsNotEmpty(ldapUserObjectClass)) return;
if (!checkConfigParameterIsNotEmpty(ldapUserCreatedTimestampAttribute)) return;
if (!checkConfigParameterIsNotEmpty(ldapUserLastModifiedTimestampAttribute)) return;
if (userAttrMap == null || userAttrMap.size() == 0) {
log
.error("Missing configuration 'userAttrMap'. Add at least the email propery to the this configuration in olatextconfig.xml first. Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
if (reqAttr == null || reqAttr.size() == 0) {
log
.error("Missing configuration 'reqAttr'. Add at least the email propery to the this configuration in olatextconfig.xml first. Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
// check if OLAT user properties is defined in olat_userconfig.xml, if not disable the LDAP module
if(!LDAPHelper.checkIfOlatPropertiesExists(userAttrMap)){
log.error("Invalid LDAP OLAT properties mapping configuration (userAttrMap). Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
if(!LDAPHelper.checkIfOlatPropertiesExists(reqAttr)){
log.error("Invalid LDAP OLAT properties mapping configuration (reqAttr). Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
if(syncOnlyOnCreateProperties != null && !LDAPHelper.checkIfStaticOlatPropertiesExists(syncOnlyOnCreateProperties)){
log.error("Invalid LDAP OLAT syncOnlyOnCreateProperties configuration. Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
if( staticUserProperties != null && !LDAPHelper.checkIfStaticOlatPropertiesExists(staticUserProperties.keySet())){
log.error("Invalid static OLAT properties configuration (staticUserProperties). Disabling LDAP");
setEnableLDAPLogins(false);
return;
}
// check SSL certifications, throws Startup Exception if certificate is not found
if(isSslEnabled()){
if (!LDAPHelper.checkServerCertValidity(0))
throw new StartupException("LDAP enabled but no valid server certificate found. Please fix!");
if (!LDAPHelper.checkServerCertValidity(30))
log.warn("Server Certificate will expire in less than 30 days.");
}
// Check ldap connection
if (ldapManager.bindSystem() == null) {
// don't disable ldap, maybe just a temporary problem, but still report
// problem in logfile
log.warn("LDAP connection test failed during module initialization, edit config or contact network administrator");
}
// OK, everything finished checkes passed
log.info("LDAP login is enabled");
/*
*
*/
// Sync LDAP Users on Startup
if (isLdapSyncOnStartup()) {
initStartSyncJob(ldapManager);
} else {
log.info("LDAP start sync is disabled");
}
// Start LDAP cron sync job
if (isLdapSyncCronSync()) {
initCronSyncJob();
} else {
log.info("LDAP cron sync is disabled");
}
// Add admin console to admin site menu
ExtManager extManager = ExtManager.getInstance();
List<LDAPAdminExtension> extensions = extManager.getExtensions();
extensions.add(new LDAPAdminExtension());
extManager.setExtensions(extensions);
// OK, everything finished checkes passed
log.info("LDAP login is enabled");
}
/**
* @see org.olat.core.configuration.OLATModule#destroy()
*/
public void destroy() {
// nothing to be destroyed
}
/**
* Internal helper to sync users right away
* @param ldapManager
*/
private void initStartSyncJob(LDAPLoginManager ldapManager) {
LDAPError errors = new LDAPError();
if (ldapManager.doBatchSync(errors)) {
log.info("LDAP start sync: users synced");
} else {
log.warn("LDAP start sync error: " + errors.get());
}
}
/**
* Internal helper to initialize the cron syncer job
*/
private void initCronSyncJob() {
//FIXME: move this to spring and add a delay otherwise the job may accesses the database and the database it not yet ready, see examples in spring with jobs
// Use scheduler from spring config
Scheduler scheduler = (Scheduler) CoreSpringFactory.getBean("schedulerFactoryBean");
try {
// Create job with cron trigger configuration
JobDetail jobDetail = new JobDetail("LDAP_Cron_Syncer_Job", Scheduler.DEFAULT_GROUP, LDAPUserSynchronizerJob.class);
CronTrigger trigger = new CronTrigger();
trigger.setName("LDAP_Cron_Syncer_Trigger");
trigger.setCronExpression(ldapSyncCronSyncExpression);
// Schedule job now
scheduler.scheduleJob(jobDetail, trigger);
log.info("LDAP cron syncer is enabled with expression::" + ldapSyncCronSyncExpression);
} catch (ParseException e) {
setLdapSyncCronSync(false);
log
.error(
"LDAP configuration in attribute 'ldapSyncCronSyncExpression' is not valid ("
+ ldapSyncCronSyncExpression
+ "). See http://quartz.sourceforge.net/javadoc/org/quartz/CronTrigger.html to learn more about the cron syntax. Disabling LDAP cron syncing",
e);
} catch (SchedulerException e) {
log.error("Error while scheduling LDAP cron sync job. Disabling LDAP cron syncing", e);
}
}
/**
* Internal helper to check for emtpy config variables
*
* @param param
* @return true: not empty; false: empty or null
*/
private boolean checkConfigParameterIsNotEmpty(String param) {
if (StringHelper.containsNonWhitespace(param)) {
return true;
} else {
log.error("Missing configuration '" + param + "'. Add this configuration to olatextconfig.xml first. Disabling LDAP");
setEnableLDAPLogins(false);
return false;
}
}
/*
* Spring setter methods - don't use them to modify values at runtime!
*/
public void setEnableLDAPLogins(boolean enableLDAPLogins) {
ldapEnabled = enableLDAPLogins;
}
public void setSslEnabled(boolean sslEnabl) {
sslEnabled = sslEnabl;
}
public void setActiveDirectory(boolean aDirectory) {
activeDirectory = aDirectory;
}
public void setTrustStoreLocation(String trustStoreLocation){
trustStoreLoc=trustStoreLocation.trim();
}
public void setTrustStorePwd(String trustStorePwd){
trustStorePass=trustStorePwd.trim();
}
public void setTrustStoreType(String trustStoreType){
trustStoreTyp= trustStoreType.trim();
}
public void setLdapSyncOnStartup(boolean ldapStartSyncs) {
ldapSyncOnStartup = ldapStartSyncs;
}
public void setLdapUserObjectClass(String objectClass) {
ldapUserObjectClass = objectClass.trim();
}
public void setLdapSystemDN(String ldapSystemDN) {
systemDN = ldapSystemDN.trim();
}
public void setLdapSystemPW(String ldapSystemPW) {
systemPW = ldapSystemPW.trim();
}
public void setLdapUrl(String ldapUrlConfig) {
ldapUrl = ldapUrlConfig.trim();
}
public void setLdapBases(List<String> ldapBasesConfig) {
ldapBases = ldapBasesConfig;
}
public void setUserAttributeMapper(Map<String, String> userAttributeMapper) {
// trim map
userAttrMap = new HashMap<String, String>();
for (Entry<String, String> entry : userAttributeMapper.entrySet()) {
userAttrMap.put(entry.getKey().trim(), entry.getValue().trim());
}
// optimizes for later usage
userAttr = userAttrMap.keySet().toArray(new String[userAttrMap.size()]);
}
public void setReqAttrs(Map<String, String> reqAttrs) {
// trim map
reqAttr = new HashMap<String, String>();
for (Entry<String, String> entry : reqAttrs.entrySet()) {
reqAttr.put(entry.getKey().trim(), entry.getValue().trim());
}
}
public void setSyncOnlyOnCreateProperties(Set<String> syncOnlyOnCreatePropertiesConfig) {
// trim map
syncOnlyOnCreateProperties = new HashSet<String>();
for (String value : syncOnlyOnCreatePropertiesConfig) {
syncOnlyOnCreateProperties.add(value.trim());
}
}
public void setStaticUserProperties(Map<String, String> staticUserPropertiesMap) {
// trim map
staticUserProperties = new HashMap<String, String>();
for (Entry<String, String> entry : staticUserPropertiesMap.entrySet()) {
staticUserProperties.put(entry.getKey().trim(), entry.getValue().trim());
}
}
public void setLdapUserLastModifiedTimestampAttribute(String ldapUserLastModifiedTimestampAttribute) {
LDAPLoginModule.ldapUserLastModifiedTimestampAttribute = ldapUserLastModifiedTimestampAttribute.trim();
}
public void setLdapUserCreatedTimestampAttribute(String ldapUserCreatedTimestampAttribute) {
LDAPLoginModule.ldapUserCreatedTimestampAttribute = ldapUserCreatedTimestampAttribute.trim();
}
public void setLdapUserPasswordAttribute(String userPasswordAttribute) {
LDAPLoginModule.ldapUserPasswordAttribute = userPasswordAttribute;
}
public void setLdapSyncCronSync(boolean ldapSyncCronSync) {
LDAPLoginModule.ldapSyncCronSync = ldapSyncCronSync;
}
public void setLdapSyncCronSyncExpression(String ldapSyncCronSyncExpression) {
LDAPLoginModule.ldapSyncCronSyncExpression = ldapSyncCronSyncExpression.trim();
}
public void setCacheLDAPPwdAsOLATPwdOnLogin(boolean cacheLDAPPwdAsOLATPwdOnLogin) {
LDAPLoginModule.cacheLDAPPwdAsOLATPwdOnLogin = cacheLDAPPwdAsOLATPwdOnLogin;
}
public void setConvertExistingLocalUsersToLDAPUsers(boolean convertExistingLocalUsersToLDAPUsers) {
LDAPLoginModule.convertExistingLocalUsersToLDAPUsers = convertExistingLocalUsersToLDAPUsers;
}
public void setDeleteRemovedLDAPUsersOnSync(boolean deleteRemovedLDAPUsersOnSync) {
LDAPLoginModule.deleteRemovedLDAPUsersOnSync = deleteRemovedLDAPUsersOnSync;
}
public void setDeleteRemovedLDAPUsersPercentage(int deleteRemovedLDAPUsersPercentage){
LDAPLoginModule.deleteRemovedLDAPUsersPercentage = deleteRemovedLDAPUsersPercentage;
}
public void setPropagatePasswordChangedOnLdapServer(boolean propagatePasswordChangedOnServer) {
LDAPLoginModule.propagatePasswordChangedOnLdapServer = propagatePasswordChangedOnServer;
}
/*
* Getters
*/
public static String getLdapSystemDN() {
return systemDN;
}
public static String getLdapSystemPW() {
return systemPW;
}
public static String getLdapUrl() {
return ldapUrl;
}
public static List<String> getLdapBases() {
return ldapBases;
}
public static String getLdapUserObjectClass() {
return ldapUserObjectClass;
}
public static String getLdapUserLastModifiedTimestampAttribute() {
return ldapUserLastModifiedTimestampAttribute;
}
public static String getLdapUserCreatedTimestampAttribute() {
return ldapUserCreatedTimestampAttribute;
}
public static String getLdapUserPasswordAttribute() {
return ldapUserPasswordAttribute;
}
/**
* @return a map of user properties to set for each LDAP user or NULL if no
* such properties have to be set
*/
public static Map<String, String> getStaticUserProperties() {
return staticUserProperties;
}
public static Map<String, String> getUserAttributeMapper() {
return userAttrMap;
}
public static String[] getUserAttrs() {
return userAttr;
}
public static Map<String, String> getReqAttrs() {
return reqAttr;
}
public static Set<String> getSyncOnlyOnCreateProperties() {
return syncOnlyOnCreateProperties;
}
public static boolean isLDAPEnabled() {
return ldapEnabled;
}
public static boolean isSslEnabled() {
return sslEnabled;
}
public static boolean isActiveDirectory() {
return activeDirectory;
}
public static String getTrustStoreLocation(){
return trustStoreLoc;
}
public static String getTrustStorePwd(){
return trustStorePass;
}
public static String getTrustStoreType(){
return trustStoreTyp;
}
public static boolean isLdapSyncOnStartup() {
return ldapSyncOnStartup;
}
public static boolean isLdapSyncCronSync() {
return ldapSyncCronSync;
}
public static String getLdapSyncCronSyncExpression() {
return ldapSyncCronSyncExpression;
}
public static boolean isCacheLDAPPwdAsOLATPwdOnLogin() {
return cacheLDAPPwdAsOLATPwdOnLogin;
}
public static boolean isConvertExistingLocalUsersToLDAPUsers() {
return convertExistingLocalUsersToLDAPUsers;
}
public static boolean isDeleteRemovedLDAPUsersOnSync() {
return deleteRemovedLDAPUsersOnSync;
}
public static int getDeleteRemovedLDAPUsersPercentage(){
return deleteRemovedLDAPUsersPercentage;
}
public static boolean isPropagatePasswordChangedOnLdapServer(){
return propagatePasswordChangedOnLdapServer;
}
}