package com.constellio.model.services.security.authentification; import java.util.Collections; import java.util.Hashtable; import javax.naming.AuthenticationException; import javax.naming.Context; import javax.naming.NamingException; import javax.naming.ldap.Control; import javax.naming.ldap.InitialLdapContext; import javax.naming.ldap.LdapContext; import com.constellio.data.dao.managers.config.ConfigManager; import com.constellio.data.utils.hashing.HashingService; import com.constellio.model.conf.ldap.LDAPConfigurationManager; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.constellio.data.dao.managers.StatefulService; import com.constellio.model.conf.ldap.LDAPDirectoryType; import com.constellio.model.conf.ldap.config.LDAPServerConfiguration; import com.constellio.model.conf.ldap.config.LDAPUserSyncConfiguration; import com.constellio.model.conf.ldap.services.LDAPServices; import com.constellio.model.conf.ldap.services.LDAPServicesException.CouldNotConnectUserToLDAP; import com.constellio.model.conf.ldap.services.LDAPServicesFactory; import com.constellio.model.conf.ldap.services.LDAPServicesImpl; import com.constellio.model.entities.security.global.UserCredential; import com.constellio.model.services.users.UserServices; import com.constellio.model.services.users.UserServicesRuntimeException.UserServicesRuntimeException_NoSuchUser; public class LDAPAuthenticationService implements AuthenticationService, StatefulService { public static final String ADMIN_USERNAME = "admin"; private LDAPServerConfiguration ldapServerConfiguration; private PasswordFileAuthenticationService adminAuthenticationService; private static final Logger LOGGER = LoggerFactory.getLogger(LDAPAuthenticationService.class); private LDAPConfigurationManager ldapConfigurationManager; private ConfigManager configManager; private HashingService hashingService; private final UserServices userServices; public Control[] connCtls = null; public LDAPAuthenticationService(LDAPConfigurationManager ldapConfigurationManager, ConfigManager configManager, HashingService hashingService, UserServices userServices) { this.ldapConfigurationManager = ldapConfigurationManager; this.configManager = configManager; this.hashingService = hashingService; this.userServices = userServices; } @Override public void initialize() { this.adminAuthenticationService = new PasswordFileAuthenticationService(configManager, hashingService); this.ldapServerConfiguration = ldapConfigurationManager.getLDAPServerConfiguration(); } @Override public void close() { } @Override public boolean authenticate(String username, String password) { if (username.equals(ADMIN_USERNAME)) { return adminAuthenticationService.authenticate(username, password); } if (StringUtils.isBlank(password)) { LOGGER.info("invalid blank password"); return false; } return authenticateLDAPUser(username, password); } private boolean authenticateLDAPUser(String username, String password) { LDAPDirectoryType directoryType = ldapServerConfiguration.getDirectoryType(); if (ldapServerConfiguration.getDirectoryType() == LDAPDirectoryType.AZURE_AD) { LDAPServices ldapServices = LDAPServicesFactory.newLDAPServices(directoryType); String userEmail = userServices.getUser(username).getEmail(); try { ldapServices.authenticateUser(ldapServerConfiguration, userEmail, password); return true; } catch (Throwable e) { LOGGER.info("Error when trying to authenticate user " + username + " with email " + userEmail, e); return false; } } else { return authenticateDefaultLDAPUser(username, password); } } //TODO : refactoring move to LDAPServicesImpl private boolean authenticateDefaultLDAPUser(String username, String password) { boolean authenticated = false; for (String url : ldapServerConfiguration.getUrls()) { authenticated = authenticate(username, password, url); if (!authenticated) { /*if(ldapServerConfiguration.getDomains().size() != 1) { String searchedDomain = getUserDomain(username, url); if(StringUtils.isNotBlank(searchedDomain)){ authenticated = authenticate( username + "@" + searchedDomain, password, url); if (authenticated) { break; } } //}*/ for (String domain : ldapServerConfiguration.getDomains()) { String userAtDomain = username + "@" + domain; authenticated = authenticate( userAtDomain, password, url); if (authenticated) { break; } } } if (authenticated) { break; } } return authenticated; } private String getUserDomain(String username, String url) { LDAPUserSyncConfiguration ldapUserSyncConfiguration = ldapConfigurationManager.getLDAPUserSyncConfiguration(); LDAPServicesImpl ldapServices = new LDAPServicesImpl(); boolean isAD = (ldapServerConfiguration.getDirectoryType() == LDAPDirectoryType.ACTIVE_DIRECTORY); LdapContext ctx = ldapServices.connectToLDAP(ldapServerConfiguration.getDomains(), url, ldapUserSyncConfiguration.getUser(), ldapUserSyncConfiguration.getPassword(), ldapServerConfiguration.getFollowReferences(), isAD); String dnForUser = ldapServices .dnForUser(ctx, username, ldapUserSyncConfiguration.getUsersWithoutGroupsBaseContextList()); try { ctx.close(); } catch (NamingException e) { //OK LOGGER.warn("Error when trying to extract user dn", e); } return dnForUser; } private boolean authenticate(String username, String password, String url) { try { String domain = StringUtils.substringAfter(username, "@"); String[] securityPrincipals = getSecurityPrincipals(username, domain, url); Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.SECURITY_AUTHENTICATION, "simple"); env.put(Context.PROVIDER_URL, url); env.put("java.naming.ldap.attributes.binary", "tokenGroups objectSid"); if (this.ldapServerConfiguration.getFollowReferences()) { env.put(Context.REFERRAL, "follow"); } if (StringUtils.startsWith(url, "ldaps")) { //env.put(Context.SECURITY_PROTOCOL, "ssl"); env.put("java.naming.ldap.factory.socket", "com.constellio.model.services.users.sync.ldaps.DummySSLSocketFactory"); } InitialLdapContext context = new InitialLdapContext(env, connCtls); for (String securityPrincipal : securityPrincipals) { context.addToEnvironment(Context.SECURITY_PRINCIPAL, securityPrincipal); context.addToEnvironment(Context.SECURITY_CREDENTIALS, password); try { context.reconnect(connCtls); return true; } catch (Exception e) { LOGGER.warn("Exception when reconnecting", e); } finally { context.close(); } } return false; } catch (AuthenticationException e) { LOGGER.warn("Exception when authenticating", e); return false; } catch (NamingException e) { LOGGER.warn("Naming exception", e); return false; } } private String[] getSecurityPrincipals(String username, String domain, String url) throws NamingException { String[] securityPrincipals; if (ldapServerConfiguration.getDirectoryType() == LDAPDirectoryType.ACTIVE_DIRECTORY) { return new String[] { username }; } else if (ldapServerConfiguration.getDirectoryType() == LDAPDirectoryType.E_DIRECTORY) { String userDn = getUserDn(username, domain, url); return new String[] { userDn }; } else { String[] prefixes = new String[] { "uid=", "cn=" }; securityPrincipals = new String[prefixes.length]; for (int i = 0; i < prefixes.length; i++) { String prefix = prefixes[i]; String usernameBeforeDomain = StringUtils.substringBefore(username, "@"); StringBuffer securityPrincipalSB = new StringBuffer(); securityPrincipalSB.append(prefix); securityPrincipalSB.append(usernameBeforeDomain); securityPrincipalSB.append(","); securityPrincipalSB.append(domain); securityPrincipals[i] = securityPrincipalSB.toString(); } } return securityPrincipals; } private String getUserDn(String username, String domain, String url) throws NamingException { try { UserCredential user = userServices.getUser(username); if (StringUtils.isNotBlank(user.getDn())) { return user.getDn(); } } catch (UserServicesRuntimeException_NoSuchUser e) { LOGGER.warn("Trying to authenticate non constellio user " + username, e); } String ldapUser = this.ldapConfigurationManager.getLDAPUserSyncConfiguration().getUser(); String ldapPassword = this.ldapConfigurationManager.getLDAPUserSyncConfiguration().getPassword(); LdapContext ldapContext = null; try { ldapContext = new LDAPServicesImpl() .connectToLDAP(Collections.EMPTY_LIST, url, ldapUser, ldapPassword, ldapServerConfiguration.getFollowReferences(), false); String usernameBeforeDomain = StringUtils.substringBefore(username, "@"); return new LDAPServicesImpl().dnForEdirectoryUser(ldapContext, domain, usernameBeforeDomain); } finally { if (ldapContext != null) { ldapContext.close(); } } } @Override public boolean supportPasswordChange() { //Passwords are managed by an other server, Constellio cannot change them return false; } @Override public void changePassword(String username, String oldPassword, String newPassword) { throw new UnsupportedOperationException("Password modification is not supported when using LDAP"); } @Override public void changePassword(String username, String newPassword) { throw new UnsupportedOperationException("Password modification is not supported when using LDAP"); } @Override public void reloadServiceConfiguration() { this.ldapServerConfiguration = ldapConfigurationManager.getLDAPServerConfiguration(); } @SuppressWarnings("serial") class FastBindConnectionControl implements Control { public byte[] getEncodedValue() { return null; } public String getID() { return "1.2.840.113556.1.4.1781"; } public boolean isCritical() { return true; } } }