/* * Copyright (c) 2013 EMC Corporation * All Rights Reserved */ package com.emc.storageos.auth.impl; import java.net.URI; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.naming.directory.SearchControls; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.ldap.AuthenticationException; import org.springframework.ldap.CommunicationException; import org.springframework.ldap.NameNotFoundException; import org.springframework.ldap.PartialResultException; import org.springframework.ldap.core.DirContextOperations; import org.springframework.ldap.core.DistinguishedName; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.support.AbstractContextMapper; import org.springframework.ldap.core.support.LdapContextSource; import org.springframework.util.CollectionUtils; import com.emc.storageos.auth.SystemPropertyUtil; import com.emc.storageos.auth.ldap.ActiveDirectoryVersionMap; import com.emc.storageos.auth.ldap.GroupWhiteList; import com.emc.storageos.auth.ldap.LdapFilterUtil; import com.emc.storageos.auth.ldap.OpenLDAPVersionChecker; import com.emc.storageos.auth.ldap.RootDSE; import com.emc.storageos.auth.ldap.RootDSEContextMapper; import com.emc.storageos.auth.ldap.RootDSELDAPContextMapper; import com.emc.storageos.auth.ldap.StorageOSLdapAuthenticationHandler; import com.emc.storageos.auth.ldap.StorageOSLdapPersonAttributeDao; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.db.client.DbClient; import com.emc.storageos.db.client.model.AuthnProvider; import com.emc.storageos.db.client.model.AuthnProvider.ProvidersType; import com.emc.storageos.db.client.model.StringSet; import com.emc.storageos.keystone.restapi.KeystoneApiClient; import com.emc.storageos.keystone.restapi.KeystoneRestClientFactory; import com.emc.storageos.model.auth.AuthnProviderParamsToValidate; import com.emc.storageos.security.exceptions.SecurityException; import com.emc.storageos.security.ssl.ViPRSSLSocketFactory; import com.emc.storageos.svcs.errorhandling.resources.APIException; /** * Utility class to encapsulate an immutable list of authentication providers. * The class is responsible for reading authn provider configurations from the database * */ public class ImmutableAuthenticationProviders { private static final String MICROSOFT_ACTIVE_DIRECTORY = "Microsoft Active Directory"; private static final String LDAP_SERVER = "LDAP Server"; private static final String CN = "CN"; private static final String OBJECT_VERSION = "objectVersion"; private static final int LDAP_VERSION_LEVEL = 3; private static final int SEARCH_CTL_COUNT_LIMIT = 1; private static final int DEFAULT_SEARCH_CTL_SCOPE = SearchControls.ONELEVEL_SCOPE; private static final HashMap<String, Integer> SEARCH_CTL_SCOPES = new HashMap<String, Integer>(); private static final String LDAPS_PROTOCOL = "ldaps"; private static final String[] LDAP_ROOT_DSE_RETURN_ATTRIBUTES = { "namingContexts", "subschemaSubentry", "supportedLDAPVersion", "supportedControl", "supportedExtension", "objectClass", "configContext", "supportedFeatures" }; private static final String[] LDAP_SCHEMA_ATTRIBUTE_TYPE_ATTRIBUTE = { "attributeTypes" }; private static final String[] LDAP_SCHEMA_OJBECT_CLASS_ATTRIBUTE = { "objectClasses" }; static { /* TODO: add more search cope mappings here if needed. For example "OBJECT" SCOPE */ SEARCH_CTL_SCOPES.put(AuthnProvider.SearchScope.ONELEVEL.toString(), SearchControls.ONELEVEL_SCOPE); SEARCH_CTL_SCOPES.put(AuthnProvider.SearchScope.SUBTREE.toString(), SearchControls.SUBTREE_SCOPE); } private final List<AuthenticationProvider> _authenticationProviders; private static Logger _log = LoggerFactory.getLogger(ImmutableAuthenticationProviders.class); private ImmutableAuthenticationProviders(final List<AuthenticationProvider> authenticationProviders) { _authenticationProviders = authenticationProviders; } public List<AuthenticationProvider> getAuthenticationProviders() { return _authenticationProviders; } /** * Factory method to retrieve an instance of this class * * @param dbclient: db client to access the configurations * @param _localAuthenticationProvider: the local auth provider, which must always be passed in and will * be added in the list first. * @param providerConfigs: provider configurations from db * @return */ public static ImmutableAuthenticationProviders getInstance(DbClient dbclient, CoordinatorClient coordinator, AuthenticationProvider _localAuthenticationProvider, List<AuthnProvider> providerConfigs) { List<AuthenticationProvider> authenticationProviders = new ArrayList<AuthenticationProvider>(); authenticationProviders.add(_localAuthenticationProvider); if (providerConfigs == null) { // bail here _log.info("Skipping load authentication providers from the database"); return new ImmutableAuthenticationProviders(authenticationProviders); } _log.info("Loading authentication providers from the database"); for (AuthnProvider authenticationConfiguration : providerConfigs) { _log.debug("Adding auth provider with ID {}", authenticationConfiguration.getId()); if (authenticationConfiguration.getInactive() || authenticationConfiguration.getDisable()) { _log.info("Skipping authentication provider {} because it is inactive", authenticationConfiguration.getId()); continue; } try { AuthenticationProvider provider = getAuthenticationProvider(coordinator, authenticationConfiguration, dbclient); if (null != provider) { authenticationProviders.add(provider); } } catch (Exception e) { _log.error("Failed to create authentication configuration {} with exception {}", authenticationConfiguration.getId(), e); } } _log.info("Loaded {} authentication handlers", authenticationProviders.size()); return new ImmutableAuthenticationProviders(authenticationProviders); } /** * Return an authentication provider based on the mode * * @param authenticationConfiguration authentication provider from the database * @param dbclient * @return An authentication provider */ public static AuthenticationProvider getAuthenticationProvider( CoordinatorClient coordinator, final AuthnProvider authenticationConfiguration, DbClient dbclient) { if (authenticationConfiguration.getMode() .equalsIgnoreCase("ad")) { _log.debug("Auth handler is in AD mode"); return getActiveDirectoryProvider(coordinator, authenticationConfiguration, dbclient); } else if (authenticationConfiguration.getMode() .equalsIgnoreCase("ldap")) { _log.debug("Auth handler is in LDAP mode"); return getLDAPProvider(coordinator, authenticationConfiguration, dbclient); } else if (AuthnProvider.ProvidersType.keystone.toString() .equalsIgnoreCase(authenticationConfiguration.getMode())) { _log.debug("Auth handler is in keystone mode"); return getKeystoneProvider(coordinator, authenticationConfiguration, dbclient); } else { _log.error( "Mode {} not known skipping this authN configuration", authenticationConfiguration.getMode()); } return null; } /** * Add keystone authentication configuration * * @param coordinator * @param authenticationConfiguration * @param dbclient * @return */ private static AuthenticationProvider getKeystoneProvider(CoordinatorClient coordinator, AuthnProvider authenticationConfiguration, DbClient dbclient) { // TODO - Construct the keystone authprovider and return return null; } /** * Add an LDAP authentication configuration * * @param authenticationConfiguration authentication provider config object */ private static AuthenticationProvider getLDAPProvider(CoordinatorClient coordinator, final AuthnProvider authenticationConfiguration, final DbClient dbclient) { LdapServerList servers = createLdapServerList(coordinator, authenticationConfiguration, SystemPropertyUtil.getLdapConnectionTimeout(coordinator)); StorageOSLdapAuthenticationHandler authHandler = createLdapAuthenticationHandler( authenticationConfiguration, servers); String[] returningAttributes = new String[] { StorageOSLdapPersonAttributeDao.COMMON_NAME, StorageOSLdapPersonAttributeDao.LDAP_DISTINGUISHED_NAME }; StorageOSLdapPersonAttributeDao attributeRepository = createLDAPAttributeRepository( dbclient, coordinator, authenticationConfiguration, servers, returningAttributes); attributeRepository.setProviderType(ProvidersType.ldap); // This is done here to differentiate with ActiveDirectory authn provider. // If we do it in the common createLDAPAttributeRepository(), there is no way // differentiate the AD and LDAP auth providers. setGroupObjectClassesAndMemberAttributes( authenticationConfiguration, attributeRepository); _log.debug("Adding LDAP mode auth handler to map"); return new AuthenticationProvider(authHandler, attributeRepository); } /** * Add an active directory authentication configuration * * @param authenticationConfiguration provider configuration object * @param dbclient */ private static AuthenticationProvider getActiveDirectoryProvider( CoordinatorClient coordinator, final AuthnProvider authenticationConfiguration, DbClient dbclient) { LdapServerList servers = createLdapServerList(coordinator, authenticationConfiguration, SystemPropertyUtil.getLdapConnectionTimeout(coordinator)); StorageOSLdapAuthenticationHandler authHandler = createLdapAuthenticationHandler( authenticationConfiguration, servers); String[] returningAttributes = new String[] { StorageOSLdapPersonAttributeDao.COMMON_NAME, StorageOSLdapPersonAttributeDao.AD_DISTINGUISHED_NAME }; StorageOSLdapPersonAttributeDao attributeRepository = createLDAPAttributeRepository(dbclient, coordinator, authenticationConfiguration, servers, returningAttributes); attributeRepository.setProviderType(ProvidersType.ad); _log.debug("Adding AD mode auth handler to map"); return new AuthenticationProvider(authHandler, attributeRepository); } /** * Create the AD/LDAP attribute repository * * @param authenticationConfiguration AD/LDAP provider configuration * @param servers AD/LDAP servers * @param returningAttributes list of attributes to return * @return StorageOSLdapPersonAttributeDao attribute repository for this configuration * @throws Exception */ private static StorageOSLdapPersonAttributeDao createLDAPAttributeRepository(DbClient dbclient, CoordinatorClient coordinator, final AuthnProvider authenticationConfiguration, LdapServerList servers, String[] returningAttributes) { GroupWhiteList groupWhiteList = createGroupWhiteList(authenticationConfiguration); StorageOSLdapPersonAttributeDao attributeRepository = new StorageOSLdapPersonAttributeDao(); attributeRepository.setLdapServers(servers); attributeRepository.setDbClient(dbclient); attributeRepository.setGroupWhiteList(groupWhiteList); if (null != authenticationConfiguration.getMaxPageSize()) { attributeRepository.setMaxPageSize(authenticationConfiguration.getMaxPageSize()); } SearchControls searchControls = new SearchControls(); searchControls.setCountLimit(SEARCH_CTL_COUNT_LIMIT); searchControls.setTimeLimit(SystemPropertyUtil.getLdapConnectionTimeout(coordinator) * 1000); searchControls.setSearchScope(convertSearchScope(authenticationConfiguration .getSearchScope())); searchControls.setReturningAttributes(returningAttributes); attributeRepository.setSearchControls(searchControls); if (null == authenticationConfiguration .getSearchFilter()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerSearchFilterCannotBeNull(authenticationConfiguration .getId()); } else { attributeRepository.setFilter(authenticationConfiguration .getSearchFilter()); } if (null == authenticationConfiguration.getSearchBase()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerSearchBaseCannotBeNull(authenticationConfiguration .getId()); } else { attributeRepository.setBaseDN(authenticationConfiguration .getSearchBase()); } return attributeRepository; } /** * Converts a search scope string into the int value to be used in the ldap * search control. Handles defaulting and bad values. * * @param searchScopeStr * @return int value to set into the ldap search control. */ private static int convertSearchScope(String searchScopeStr) { Integer scopeValueI = DEFAULT_SEARCH_CTL_SCOPE; if (searchScopeStr == null) { _log.debug("Search scope not provided. Using default one level"); } else { scopeValueI = SEARCH_CTL_SCOPES.get(searchScopeStr); if (scopeValueI == null) { _log.debug("Could not convert search scope parameter value {}", searchScopeStr); } _log.debug("Provided search scope is: {}", searchScopeStr); } _log.debug("Search scope to be used is: {}", scopeValueI); return scopeValueI == null ? DEFAULT_SEARCH_CTL_SCOPE : scopeValueI; } /** * Create group whitelist * * @param authenticationConfiguration provider configuration containing the whitelist parameters * @return Group whitelist for this configuration */ private static GroupWhiteList createGroupWhiteList( final AuthnProvider authenticationConfiguration) { GroupWhiteList whiteList = new GroupWhiteList(); whiteList .setType(authenticationConfiguration.getGroupAttribute() == null ? CN : authenticationConfiguration.getGroupAttribute()); whiteList.setValues(authenticationConfiguration .getGroupWhitelistValues() != null ? authenticationConfiguration .getGroupWhitelistValues().toArray( new String[authenticationConfiguration .getGroupWhitelistValues().size()]) : new String[0]); return whiteList; } /** * Create the authentication handler for this AD/LDAP configuration * * @param authenticationConfiguration AD/LDAP provider configuration * @param servers AD/LDAP servers * @return BindLdapAuthenticationHandler generated from configuration * @throws Exception */ private static StorageOSLdapAuthenticationHandler createLdapAuthenticationHandler( final AuthnProvider authenticationConfiguration, LdapServerList servers) { StorageOSLdapAuthenticationHandler authHandler = new StorageOSLdapAuthenticationHandler(); if (null == authenticationConfiguration .getSearchFilter()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerSearchFilterCannotBeNull(authenticationConfiguration .getId()); } else { authHandler.setFilter(authenticationConfiguration .getSearchFilter()); } if (null == authenticationConfiguration.getSearchBase()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerSearchBaseCannotBeNull(authenticationConfiguration .getId()); } else { authHandler.setSearchBase(authenticationConfiguration .getSearchBase()); } if (null == authenticationConfiguration.getDomains()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerDomainsCannotBeNull(authenticationConfiguration .getId()); } else { authHandler.setDomains(authenticationConfiguration.getDomains()); } authHandler.setLdapServers(servers); return authHandler; } private static LdapServerList createLdapServerList( CoordinatorClient coordinator, AuthnProvider authenticationConfiguration, int timeout) { LdapServerList servers = new LdapServerList(); for (String url : authenticationConfiguration.getServerUrls()) { LdapOrADServer server = createLdapOrAdServer(coordinator, authenticationConfiguration, timeout, url); servers.add(server); } return servers; } private static ArrayList<LdapContextSource> createLDAPContextSources( CoordinatorClient coordinator, final AuthnProvider authenticationConfiguration, final Map<String, String> environmentProperties) { ArrayList<LdapContextSource> ctxSources = new ArrayList<>(); for (String url : authenticationConfiguration.getServerUrls()) { LdapContextSource ctx = createLDAPContextSource(coordinator, authenticationConfiguration, environmentProperties, url); ctxSources.add(ctx); } return ctxSources; } private static LdapOrADServer createLdapOrAdServer( CoordinatorClient coordinator, AuthnProvider authenticationConfiguration, int timeout, String url) { LdapOrADServer server = new LdapOrADServer(); server.setContextSource(createConfiguredLDAPContextSource(coordinator, authenticationConfiguration, timeout, url)); return server; } /** * Return AD/LDAP context source generated from the configuration * * @param authenticationConfiguration The AD/LDAP authentication provider configuration * @param environmentProperties bas environment properties for the context source * @return LdapContextSource the context source generated from the configuration */ private static LdapContextSource createLDAPContextSource( CoordinatorClient coordinator, final AuthnProvider authenticationConfiguration, final Map<String, String> environmentProperties, String serverUrl) { LdapContextSource contextSource = new LdapContextSource(); contextSource.setAnonymousReadOnly(false); contextSource.setPooled(false); if (null == authenticationConfiguration.getManagerDN() || null == authenticationConfiguration .getManagerPassword()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerManagerUserDNPasswordAreRequired(authenticationConfiguration .getId()); } else { contextSource.setUserDn(authenticationConfiguration .getManagerDN()); contextSource.setPassword(authenticationConfiguration .getManagerPassword()); } if (null == authenticationConfiguration.getServerUrls()) { throw APIException.badRequests .failedToCreateAuthenticationHandlerServerURLsAreRequired(authenticationConfiguration .getId()); } else { contextSource.setUrl(serverUrl); if (contextSource.getUrls()[0].toLowerCase().startsWith(LDAPS_PROTOCOL)) { environmentProperties.put("java.naming.ldap.factory.socket", ViPRSSLSocketFactory.class.getName()); } } if (null != environmentProperties) { contextSource.setBaseEnvironmentProperties(environmentProperties); } try { contextSource.afterPropertiesSet(); } catch (Exception ex) { _log.error("exception from context source initialization for provider {}", authenticationConfiguration.getId(), ex); // TODO - is this a transient error or config error? throw SecurityException.fatals .exceptionFromContextSourceInitializationForProvider( authenticationConfiguration.getId(), ex); } return contextSource; } /** * Creates an LDAPContextSource with the given provider object and optional timeout * * @param authProvider the authentication provider object * @param timeout: pass a value less than 1 if you wish not to use it. * @return LdapContextSource */ public static LdapContextSource createConfiguredLDAPContextSource( CoordinatorClient coordinator, AuthnProvider authProvider, int timeout, String url) { Map<String, String> environmentProperties = new HashMap<String, String>(); environmentProperties.put("java.naming.security.authentication", "simple"); if (authProvider.getMode().equalsIgnoreCase(AuthnProvider.ProvidersType.ad.toString())) { environmentProperties.put("java.naming.ldap.attributes.binary", StorageOSLdapPersonAttributeDao.TOKEN_GROUPS + " " + StorageOSLdapPersonAttributeDao.OBJECT_SID); } if (!authProvider.getServerUrls().iterator().next().toLowerCase().startsWith(LDAPS_PROTOCOL) && timeout > 1) { // the timeout property cannot be used with SSL because of: // http://www-01.ibm.com/support/docview.wss?uid=swg24010108 environmentProperties.put("com.sun.jndi.ldap.connect.timeout", String.valueOf((timeout * 1000))); } return createLDAPContextSource(coordinator, authProvider, environmentProperties, url); } /** * Verifies basic connectivity of the provider by attempting a connection with * the manager DN and password to the provided url * * @param param contains the connection parameter * @param errorString will contain the message from the exception in case an exception is * @return true if success, false if failure */ public static boolean checkProviderStatus(CoordinatorClient coordinator, final AuthnProviderParamsToValidate param, KeystoneRestClientFactory keystoneFactory, StringBuilder errorString, DbClient dbClient) { AuthnProvider authConfig = new AuthnProvider(); authConfig.setManagerDN(param.getManagerDN()); authConfig.setManagerPassword(param.getManagerPwd()); StringSet urls = new StringSet(); urls.addAll(param.getUrls()); authConfig.setServerUrls(urls); if (AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(param.getMode())) { authConfig.setMode(AuthnProvider.ProvidersType.keystone.toString()); checkKeystoneProviderConnectivity(authConfig, keystoneFactory); return true; } else { authConfig.setMode(AuthnProvider.ProvidersType.ldap.toString()); // we don't need AD specifics here } LdapServerList servers = createLdapServerList(coordinator, authConfig, SystemPropertyUtil.getLdapConnectionTimeout(coordinator)); _log.info("Checking the status of the provider whose urls are {}", param.getUrls()); boolean good = false; // Checking in order and return good if meeting one good. for (LdapOrADServer server : servers.getConnectedServers()) { good = doCheckProviderStatusOnSingleServer(server, param, errorString, dbClient); if (good) { _log.info("Checked provider against server {} successfully", server.getContextSource().getUrls()[0]); return true; } } return false; } /** * Check AD/Ldap provider against one of servers input * @param server * @param param * @param errorString * @param dbClient * @return */ public static boolean doCheckProviderStatusOnSingleServer( LdapOrADServer server, AuthnProviderParamsToValidate param, StringBuilder errorString, DbClient dbClient) { LdapTemplate template = new LdapTemplate(server.getContextSource()); template.setIgnorePartialResultException(true); if (!checkManagerDNAndSearchBase(template, param, errorString)) { return false; } boolean isLDAPMode = true; if (param.getMode().equals(AuthnProvider.ProvidersType.ad.toString())) { isLDAPMode = false; } // record RootDSE server metadata, including directory type/vendor, supported LDAP versions, // and make sure mode matches with AD/LDAP server RootDSE rootDSE = null; if (isLDAPMode == true) { rootDSE = getRootDSE(template, LDAP_ROOT_DSE_RETURN_ATTRIBUTES, new RootDSELDAPContextMapper()); } else { rootDSE = getRootDSE(template, null, new RootDSEContextMapper()); } if (rootDSE == null) { return false; } if (!checkDirectoryType(template, rootDSE, param, errorString)) { return false; } if (isLDAPMode == true) { if (dbClient.checkGeoCompatible(AuthnProvider.getExpectedGeoVDCVersionForLDAPGroupSupport())) { if (!checkLDAPGroupAttribute(template, rootDSE, param, errorString)) { return false; } if (!checkLDAPGroupObjectClasses(template, rootDSE, param, errorString)) { return false; } if (!checkLDAPGroupMemberAttributes(template, rootDSE, param, errorString)) { return false; } } return true; } else { return checkGroupAttribute(template, rootDSE, param, errorString); } } /** * Checks the keystone provider status * * @param authConfig */ private static void checkKeystoneProviderConnectivity(AuthnProvider authConfig, KeystoneRestClientFactory keystoneFactory) { String managerDn = authConfig.getManagerDN(); String password = authConfig.getManagerPassword(); StringSet uris = authConfig.getServerUrls(); String userName = ""; String tenantName = ""; try { String[] managerdnArray = managerDn.split(","); String firstEle = managerdnArray[0]; String secondEle = managerdnArray[1]; userName = firstEle.split("=")[1]; tenantName = secondEle.split("=")[1]; } catch (Exception ex) { throw APIException.badRequests.managerDNInvalid(); } URI authUri = null; for (String uri : uris) { authUri = URI.create(uri); break; // There will be single URL only } KeystoneApiClient keystoneApi = (KeystoneApiClient) keystoneFactory.getRESTClient( authUri, userName, password); keystoneApi.setTenantName(tenantName); keystoneApi.authenticate_keystone(); } /** * Validates the connection to LDAP and manager DN credentials, search base * * @param template the ldap template to use * @param param the param structure containing the parameters to validate * @param errorString output parameter to store error string * @return true if validation succeeded. false otherwise */ private static boolean checkManagerDNAndSearchBase(LdapTemplate template, final AuthnProviderParamsToValidate param, StringBuilder errorString) { try { // authenticates manager credentials and performs the look up for the search base template.lookup(new DistinguishedName(param.getSearchBase())); return true; } catch (CommunicationException e) { errorString .append(MessageFormat .format("Connection to LDAP server {0} failed. Please, check the scheme, accessibility of the LDAP server and port. LDAP error: {1}.", param.getUrls().toString(), stripNonPrintableCharacters(e.getMessage()))); _log.debug("Connection to LDAP server " + param.getUrls().toString() + " failed.", e); return false; } catch (AuthenticationException e) { errorString .append(MessageFormat .format("Connection to the LDAP server {0} succeeded but the Manager DN {1} or its password failed to authenticate. LDAP error: {2}", param.getUrls().toString(), param.getManagerDN(), stripNonPrintableCharacters(e.getMessage()))); return false; } catch (NameNotFoundException e) { errorString .append(MessageFormat .format("Connection to the LDAP server {0} succeeded and the Manager DN authenticated successfully but the search base path {1} could not be found in the LDAP tree. LDAP error: {2}", param.getUrls().toString(), param.getSearchBase(), stripNonPrintableCharacters(e.getMessage()))); return false; } catch (PartialResultException e) { errorString .append(MessageFormat .format("Connection to the LDAP server {0} succeeded and the Manager DN authenticated successfully but a portion of the search base path {1} could not be found in the LDAP tree. LDAP error: {2}", param.getUrls().toString(), param.getSearchBase(), stripNonPrintableCharacters(e.getMessage()))); return false; } catch (Exception e) { errorString.append(MessageFormat.format( "Validation of the Manager DN {0} and search base {1} against the LDAP server {2} failed because of LDAP error: {3}", param.getManagerDN(), param.getSearchBase(), param.getUrls().toString(), stripNonPrintableCharacters(e.getMessage()))); return false; } } /** * Queries the AD schema to check that the group attribute exists * * @param template the ldap template to use * @param rootDSE the RootDSE object * @param param the param structure containing the parameters to validate * @param errorString output parameter to store error string * @return true if validation succeeded. false otherwise */ @SuppressWarnings("rawtypes") private static boolean checkGroupAttribute(LdapTemplate template, final RootDSE rootDSE, final AuthnProviderParamsToValidate param, StringBuilder errorString) { try { // retrieve the rootDSE's schemaNamingContext operational attribute String schemaDN = rootDSE.getSchemaNamingContext(); // query for the attribute List list = template.search(schemaDN, LdapFilterUtil.getAttributeFilterWithValues(param.getGroupAttr()), SearchControls.ONELEVEL_SCOPE, new AbstractContextMapper() { @Override protected Object doMapFromContext(DirContextOperations ctx) { return ctx.getStringAttribute("cn"); } }); if (CollectionUtils.isEmpty(list)) { errorString.append(MessageFormat.format("The group attribute {0} could not be found in AD schema at server {1}.", param.getGroupAttr(), param.getUrls().toString())); return false; } else { _log.debug("Found attribute: {} {}", list.get(0), param.getGroupAttr()); } return true; } catch (CommunicationException e) { errorString.append(MessageFormat.format( "Connection to LDAP server {0} failed during search for group attribute {1}. LDAP error: {2}", param.getUrls().toString(), param.getGroupAttr(), stripNonPrintableCharacters(e.getMessage()))); return false; } catch (Exception e) { errorString.append(MessageFormat.format( "Validation of group attribute {0} against server {1} failed because of LDAP error: {2}", param.getGroupAttr(), param.getUrls().toString(), stripNonPrintableCharacters(e.getMessage()))); return false; } } /** * Retrieve AD/LDAP's RootDSE. * * @param template - A AD/LDAP template to be searched. * * @return - RootDSE object */ private static RootDSE getRootDSE(LdapTemplate template, String[] returnAttributes, AbstractContextMapper contextMapper) { // retrieve the rootDSE @SuppressWarnings("rawtypes") List list = template.search("", "(objectclass=*)", SearchControls.OBJECT_SCOPE, returnAttributes, contextMapper); if (CollectionUtils.isEmpty(list)) { _log.error("Could not query RootDSE for AD/LDAP"); return null; } RootDSE rootDSE = (RootDSE) list.get(0); _log.info("RootDSE: {}", rootDSE.toString()); return rootDSE; } /** * Check directory type/vendor, supported LDAP versions, * and make sure configured mode matches with AD/LDAP server type * * @param template the ldap template to use * @param rootDSE the RootDSE object * @param param the param structure containing the parameters to validate * @param errorString output parameter to store error string * @return true if validation succeeded. false otherwise */ @SuppressWarnings("rawtypes") private static boolean checkDirectoryType(LdapTemplate template, RootDSE rootDSE, final AuthnProviderParamsToValidate param, StringBuilder errorString) { // check LDAP version boolean ldapVersionPassed = false; if (rootDSE.getSupportedLDAPVersion() != null) { for (int i = 0; i < rootDSE.getSupportedLDAPVersion().length; i++) { if (rootDSE.getSupportedLDAPVersion()[i] >= LDAP_VERSION_LEVEL) { ldapVersionPassed = true; break; } } if (!ldapVersionPassed) { String errorMsg = MessageFormat.format("Supported LDAP version is insufficient at server {0}: must be at least {1}.", param.getUrls().toString(), LDAP_VERSION_LEVEL); errorString.append(errorMsg); _log.error(errorMsg); return false; } } else { _log.warn("Failed to get supported LDAP versions at server {}", param.getUrls().toString()); } String serverType = null; // check active directory String rootDomainNamingContext = rootDSE.getRootDomainNamingContext(); if (rootDomainNamingContext == null || rootDomainNamingContext.equals("")) { serverType = LDAP_SERVER; if (!param.getMode().equals("ldap")) { String errorMsg = MessageFormat.format("Directory server type LDAP doesn't match with specified mode {1} at server {0}.", param.getUrls().toString(), param.getMode()); errorString.append(errorMsg); _log.error(errorMsg); return false; } serverType = OpenLDAPVersionChecker.getOpenLDAPVersion(rootDSE); if (serverType == null) { serverType = LDAP_SERVER; } _log.info("Server type: {} at {}", serverType, param.getUrls().toString()); } else { serverType = MICROSOFT_ACTIVE_DIRECTORY; if (!param.getMode().equals("ad")) { // using AD as LDAP server only will not take advantage of AD specific features, but it is allowed String errorMsg = MessageFormat.format( "Directory server type Active Directory doesn't match with specified mode {1} at server {0}.", param.getUrls().toString(), param.getMode()); _log.warn(errorMsg); } // retrieve the rootDSE's schemaNamingContext operational attribute String schemaDN = rootDSE.getSchemaNamingContext(); if (schemaDN == null || schemaDN.equals("")) { String errorMsg = MessageFormat.format("Could not find Schema Naming Context for server {0}", param.getUrls().toString()); errorString.append(errorMsg); _log.error(errorMsg); return false; } else { _log.debug("Found Schema DN: {} for server {}", schemaDN, param.getUrls().toString()); } // check and record objectVersion, windows server type try { List list = template.search(schemaDN, "(objectclass=*)", SearchControls.OBJECT_SCOPE, new AbstractContextMapper() { @Override protected Object doMapFromContext(DirContextOperations ctx) { return ctx.getStringAttribute(OBJECT_VERSION); } }); if (CollectionUtils.isEmpty(list)) { String errorMsg = MessageFormat.format("The attribute {0} could not be found in AD schema at server {1}.", OBJECT_VERSION, param.getUrls().toString()); errorString.append(errorMsg); _log.error(errorMsg); return false; } String objectVersion = (String) list.get(0); String windowsServer = ActiveDirectoryVersionMap.getActiveDirectoryVersion(objectVersion); String infoMsg = MessageFormat .format("Active Directory server information {0} - server type: {1}, objectVersion: {2}, Microsoft Windows Server version: {3}", param.getUrls().toString(), serverType, objectVersion, windowsServer); _log.info(infoMsg); return true; } catch (CommunicationException e) { String errorMsg = MessageFormat .format("Connection to Active Directory server {0} failed during query of schema DN ({1})'s objectVersion attribute. LDAP error: {2}", param.getUrls().toString(), schemaDN, stripNonPrintableCharacters(e.getMessage())); errorString.append(errorMsg); _log.error(errorMsg); return false; } catch (Exception e) { String errorMsg = MessageFormat.format("Query {0} against server {1} failed because of LDAP error: {2}", OBJECT_VERSION, param.getUrls().toString(), stripNonPrintableCharacters(e.getMessage())); errorString.append(errorMsg); _log.error(errorMsg); return false; } } return true; } /** * removes unprintable chartacters from input string * * @param input * @return cleaned string */ private static String stripNonPrintableCharacters(String input) { return input.replaceAll("[\\x00-\\x1F]", ""); } /** * Method that sets the Group's objectClasses and member attributes * that will be used to search the corresponding group in the LDAP. * This is called only from the LDAP authn provider creation, just to make * sure that, these values are used only for the LDAP authn providers. * * @param authenticationConfiguration - Configuration of an authn provider. * @param attributeRepository - Authn provider repository. * */ private static void setGroupObjectClassesAndMemberAttributes( final AuthnProvider authenticationConfiguration, StorageOSLdapPersonAttributeDao attributeRepository) { if (null != authenticationConfiguration.getGroupObjectClassNames()) { attributeRepository.getGroupObjectClasses().addAll(authenticationConfiguration.getGroupObjectClassNames()); _log.debug("Adding group object classes {} for LDAP", attributeRepository.getGroupObjectClasses()); } if (null != authenticationConfiguration.getGroupMemberAttributeTypeNames()) { attributeRepository.getGroupMemberAttributes().addAll(authenticationConfiguration.getGroupMemberAttributeTypeNames()); _log.debug("Adding group member attributes {} for LDAP", attributeRepository.getGroupMemberAttributes()); } } /** * A method that searches LDAP schema to find if the given attribute is * a valid attribute of the schema or not. These attributes include * attributeTypes, objectClasses, etc. * * @param template - A LDAP template to be searched. * @param rootDSE - Root DSE of the LDAP. * @param errorString - Error string to be returned in case of any errors * during the search. * * @return - List of all the attributes of the LDAP schema that matches * search criteria. */ private static List<List<String>> searchInLDAPSchema(LdapTemplate template, String[] returnAttributes, final RootDSE rootDSE, final List<String> ldapServerUrls, StringBuilder errorString) { try { // retrieve the rootDSE's schemaNamingContext operational attribute String schemaDN = rootDSE.getSchemaNamingContext(); _log.debug("Searching in LDAP schema DN {} ", schemaDN); // query for the attribute @SuppressWarnings("unchecked") List<List<String>> attributeList = template.search(schemaDN, "(objectclass=*)", SearchControls.OBJECT_SCOPE, returnAttributes, new LDAPSchemaContextMapper()); if (CollectionUtils.isEmpty(attributeList)) { errorString.append(MessageFormat.format("The attributes {0} could not be found in LDAP schema {1} at server {2}", returnAttributes.toString(), schemaDN, ldapServerUrls.toString())); } return attributeList; } catch (CommunicationException e) { errorString.append(MessageFormat.format( "Connection to LDAP server {0} failed during search for attribute {1}. LDAP error: {2}", ldapServerUrls.toString(), returnAttributes.toString(), stripNonPrintableCharacters(e.getMessage()))); return null; } catch (Exception e) { errorString.append(MessageFormat.format( "Exception during attribute {0} search against server {1} failed because of LDAP error: {2}", returnAttributes.toString(), ldapServerUrls.toString(), stripNonPrintableCharacters(e.getMessage()))); return null; } } /** * Queries the LDAP schema to check that the group attribute exists * * @param template - A LDAP template to be searched. * @param rootDSE - Root DSE of the LDAP. * @param errorString - Error string to be returned in case of any errors * during the search. * * @return - List of all the attributes of the LDAP schema that matches * search criteria. */ private static boolean checkLDAPGroupAttribute(LdapTemplate template, final RootDSE rootDSE, final AuthnProviderParamsToValidate param, StringBuilder errorString) { boolean isValidGroupAttribute = false; String schemaDN = rootDSE.getSchemaNamingContext(); List<List<String>> groupAttributeLists = searchInLDAPSchema(template, LDAP_SCHEMA_ATTRIBUTE_TYPE_ATTRIBUTE, rootDSE, param.getUrls(), errorString); if (CollectionUtils.isEmpty(groupAttributeLists)) { return isValidGroupAttribute; } String groupAttributeToValidate = param.getGroupAttr(); for (List<String> groupAttributeList : groupAttributeLists) { for (String groupAttribute : groupAttributeList) { if (groupAttribute.equalsIgnoreCase(groupAttributeToValidate)) { isValidGroupAttribute = true; _log.debug("Found group attribute {} in LDAP schema {}", groupAttributeToValidate, schemaDN); break; } } if (isValidGroupAttribute) { break; } } if (!isValidGroupAttribute) { errorString.append(MessageFormat.format("Could not find group attribute {0} in LDAP schema {1} at {2}", param.getGroupAttr(), schemaDN, param.getUrls().toString())); } return isValidGroupAttribute; } /** * Queries the LDAP schema to check that the group objectClass exists * * @param template - A LDAP template to be searched. * @param rootDSE - Root DSE of the LDAP. * @param errorString - Error string to be returned in case of any errors * during the search. * * @return - List of all the attributes of the LDAP schema that matches * search criteria. */ private static boolean checkLDAPGroupObjectClasses(LdapTemplate template, RootDSE rootDSE, final AuthnProviderParamsToValidate param, StringBuilder errorString) { boolean isValidGroupObjectClasses = true; String schemaDN = rootDSE.getSchemaNamingContext(); Set<String> errorGroupObjectClasses = null; List<List<String>> groupObjectClassLists = searchInLDAPSchema(template, LDAP_SCHEMA_OJBECT_CLASS_ATTRIBUTE, rootDSE, param.getUrls(), errorString); if (CollectionUtils.isEmpty(groupObjectClassLists)) { isValidGroupObjectClasses = false; } else { errorGroupObjectClasses = new HashSet<String>(); for (String expGroupObjectClass : param.getGroupObjectClasses()) { boolean groupObjectClassFound = false; for (List<String> groupObjectClassList : groupObjectClassLists) { for (String groupObjectClass : groupObjectClassList) { if (groupObjectClass.equalsIgnoreCase(expGroupObjectClass)) { _log.debug("Found objectClass {} in LDAP schema {}", expGroupObjectClass, schemaDN); groupObjectClassFound = true; break; } } if (groupObjectClassFound) { break; } } if (!groupObjectClassFound) { errorGroupObjectClasses.add(expGroupObjectClass); } } } if (!CollectionUtils.isEmpty(errorGroupObjectClasses)) { errorString.append(MessageFormat.format("Could not find objectClasses {0} in LDAP schema {1} at {2}", errorGroupObjectClasses.toString(), schemaDN, param.getUrls().toString())); isValidGroupObjectClasses = false; } return isValidGroupObjectClasses; } /** * Queries the LDAP schema to check that the group attribute type exists * * @param template - A LDAP template to be searched. * @param rootDSE - Root DSE of the LDAP. * @param errorString - Error string to be returned in case of any errors * during the search. * * @return - List of all the attributes of the LDAP schema that matches * search criteria. */ private static boolean checkLDAPGroupMemberAttributes(LdapTemplate template, RootDSE rootDSE, AuthnProviderParamsToValidate param, StringBuilder errorString) { boolean isValidGroupMemberAttributes = true; String schemaDN = rootDSE.getSchemaNamingContext(); Set<String> errorGroupMemberAttributes = null; List<List<String>> groupMemberAttributeLists = searchInLDAPSchema(template, LDAP_SCHEMA_ATTRIBUTE_TYPE_ATTRIBUTE, rootDSE, param.getUrls(), errorString); if (CollectionUtils.isEmpty(groupMemberAttributeLists)) { isValidGroupMemberAttributes = false; } else { errorGroupMemberAttributes = new HashSet<String>(); for (String expGroupMemberAttribute : param.getGroupMemberAttributes()) { boolean groupMemberAttributeFound = false; for (List<String> groupMemberAttributeList : groupMemberAttributeLists) { for (String groupMemberAttribute : groupMemberAttributeList) { if (groupMemberAttribute.equalsIgnoreCase(expGroupMemberAttribute)) { _log.debug("Found member attribute {} in LDAP schema {}", expGroupMemberAttribute, schemaDN); groupMemberAttributeFound = true; break; } } if (groupMemberAttributeFound) { break; } } if (!groupMemberAttributeFound) { errorGroupMemberAttributes.add(expGroupMemberAttribute); } } } if (!CollectionUtils.isEmpty(errorGroupMemberAttributes)) { errorString.append(MessageFormat.format("Could not find attributes {0} in LDAP schema {1} at {2}", errorGroupMemberAttributes.toString(), schemaDN, param.getUrls().toString())); isValidGroupMemberAttributes = false; } return isValidGroupMemberAttributes; } }