/* * Licensed to Jasig under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Jasig licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a * copy of the License at the following location: * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.jasig.cas.persondir; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.annotation.PostConstruct; import javax.naming.directory.SearchControls; import javax.validation.constraints.NotNull; import org.jasig.cas.util.LdapUtils; import org.jasig.services.persondir.IPersonAttributes; import org.jasig.services.persondir.support.AbstractQueryPersonAttributeDao; import org.jasig.services.persondir.support.CaseInsensitiveAttributeNamedPersonImpl; import org.jasig.services.persondir.support.CaseInsensitiveNamedPersonImpl; import org.ldaptive.Connection; import org.ldaptive.ConnectionFactory; import org.ldaptive.LdapAttribute; import org.ldaptive.LdapEntry; import org.ldaptive.LdapException; import org.ldaptive.Response; import org.ldaptive.SearchFilter; import org.ldaptive.SearchOperation; import org.ldaptive.SearchRequest; import org.ldaptive.SearchResult; import org.ldaptive.SearchScope; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Person directory <code>IPersonAttribute</code> implementation that queries an LDAP directory * with ldaptive components to populate person attributes. * * @author Marvin S. Addison * @since 4.0 */ public class LdapPersonAttributeDao extends AbstractQueryPersonAttributeDao<SearchFilter> { /** Logger instance. **/ protected Logger logger = LoggerFactory.getLogger(this.getClass()); /** Search base DN. */ @NotNull private String baseDN; /** Search controls. */ @NotNull private SearchControls searchControls; /** LDAP connection factory. */ @NotNull private ConnectionFactory connectionFactory; /** LDAP search scope. */ private SearchScope searchScope; /** LDAP search filter. */ @NotNull private String searchFilter; /** LDAP attributes to fetch from search results. */ private String[] attributes; /** * Sets the base DN of the LDAP search for attributes. * * @param dn LDAP base DN of search. */ public void setBaseDN(final String dn) { this.baseDN = dn; } /** * Sets the LDAP search filter used to query for person attributes. * * @param filter Search filter of the form "(usernameAttribute={0})" where {0} and similar ordinal placeholders * are replaced with query parameters. */ public void setSearchFilter(final String filter) { this.searchFilter = filter; } /** * Sets a number of parameters that control LDAP search semantics including search scope, * maximum number of results retrieved, and search timeout. * * @param searchControls LDAP search controls. */ public void setSearchControls(final SearchControls searchControls) { this.searchControls = searchControls; } /** * Sets the connection factory that produces LDAP connections on which searches occur. It is strongly recommended * that this be a <code>PooledConnecitonFactory</code> object. * * @param connectionFactory LDAP connection factory. */ public void setConnectionFactory(final ConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } /** * Initializes the object after properties are set. */ @PostConstruct public void initialize() { for (final SearchScope scope : SearchScope.values()) { if (scope.ordinal() == this.searchControls.getSearchScope()) { this.searchScope = scope; } } this.attributes = getResultAttributeMapping().keySet().toArray(new String[getResultAttributeMapping().size()]); } @Override protected List<IPersonAttributes> getPeopleForQuery(final SearchFilter filter, final String userName) { Connection connection = null; try { try { connection = this.connectionFactory.getConnection(); } catch (final LdapException e) { throw new RuntimeException("Failed getting LDAP connection", e); } final Response<SearchResult> response; try { response = new SearchOperation(connection).execute(createRequest(filter)); } catch (final LdapException e) { throw new RuntimeException("Failed executing LDAP query " + filter, e); } final SearchResult result = response.getResult(); final List<IPersonAttributes> peopleAttributes = new ArrayList<IPersonAttributes>(result.size()); for (final LdapEntry entry : result.getEntries()) { final IPersonAttributes person; final String userNameAttribute = this.getConfiguredUserNameAttribute(); final Map<String, List<Object>> attributes = convertLdapEntryToMap(entry); if (attributes.containsKey(userNameAttribute)) { person = new CaseInsensitiveAttributeNamedPersonImpl(userNameAttribute, attributes); } else { person = new CaseInsensitiveNamedPersonImpl(userName , attributes); } peopleAttributes.add(person); } return peopleAttributes; } finally { LdapUtils.closeConnection(connection); } } @Override protected SearchFilter appendAttributeToQuery( final SearchFilter filter, final String attribute, final List<Object> values) { final SearchFilter query; if (filter == null && values.size() > 0) { query = new SearchFilter(this.searchFilter); query.setParameter(0, values.get(0).toString()); logger.debug("Constructed LDAP search query [{}]", query.format()); } else { throw new UnsupportedOperationException("Multiple attributes not supported."); } return query; } /** * Creates a search request from a search filter. * * @param filter LDAP search filter. * * @return ldaptive search request. */ private SearchRequest createRequest(final SearchFilter filter) { final SearchRequest request = new SearchRequest(); request.setBaseDn(this.baseDN); request.setSearchFilter(filter); request.setReturnAttributes(this.attributes); request.setSearchScope(this.searchScope); request.setSizeLimit(this.searchControls.getCountLimit()); request.setTimeLimit(this.searchControls.getTimeLimit()); return request; } /** * Converts an ldaptive <code>LdapEntry</code> containing result entry attributes into an attribute map as needed * by Person Directory components. * * @param entry Ldap entry. * * @return Attribute map. */ private Map<String, List<Object>> convertLdapEntryToMap(final LdapEntry entry) { final Map<String, List<Object>> attributeMap = new LinkedHashMap<String, List<Object>>(entry.size()); for (final LdapAttribute attr : entry.getAttributes()) { attributeMap.put(attr.getName(), new ArrayList<Object>(attr.getStringValues())); } logger.debug("Converted ldap DN entry [{}] to attribute map {}", entry.getDn(), attributeMap.toString()); return attributeMap; } }