/** * Licensed to Apereo under one or more contributor license * agreements. See the NOTICE file distributed with this work * for additional information regarding copyright ownership. * Apereo 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.portlet.blackboardvcportlet.service.impl; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import org.apache.commons.lang.StringUtils; import org.jasig.portlet.blackboardvcportlet.data.BasicUser; import org.jasig.portlet.blackboardvcportlet.data.BasicUserImpl; import org.jasig.portlet.blackboardvcportlet.service.UserService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.IncorrectResultSizeDataAccessException; import org.springframework.dao.support.DataAccessUtils; import org.springframework.ldap.core.AttributesMapper; import org.springframework.ldap.core.LdapOperations; import org.springframework.ldap.filter.AndFilter; import org.springframework.ldap.filter.EqualsFilter; import org.springframework.ldap.filter.LikeFilter; import org.springframework.ldap.filter.OrFilter; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet.Builder; /** * Service Class for retrieving LDAP user lookups * @author Richard Good */ public class LdapUserServiceImpl implements UserService { private static final Pattern NAME_SPLIT = Pattern.compile("\\s+"); protected final Logger logger = LoggerFactory.getLogger(getClass()); private final BasicUserAttributeMapper basicUserAttributeMapper = new BasicUserAttributeMapper(); private LdapOperations ldapOperations; private String uniqueIdAttributeName = "uid"; private String displayNameAttributeName = "cn"; private String firstNameAttributeName = "givenName"; private String lastNameAttributeName = "sn"; private String mailAttributeName = "wisceduallemails"; public void setUniqueIdAttributeName(String uniqueIdAttributeName) { this.uniqueIdAttributeName = uniqueIdAttributeName; } public void setDisplayNameAttributeName(String displayNameAttributeName) { this.displayNameAttributeName = displayNameAttributeName; } public void setFirstNameAttributeName(String firstNameAttributeName) { this.firstNameAttributeName = firstNameAttributeName; } public void setLastNameAttributeName(String lastNameAttributeName) { this.lastNameAttributeName = lastNameAttributeName; } public void setMailAttributeName(String mailAttributeName) { this.mailAttributeName = mailAttributeName; } @Autowired public void setLdapOperations(LdapOperations ldapOperations) { this.ldapOperations = ldapOperations; } @Override public BasicUser findUser(String uniqueId) { final AndFilter andFilter = createBaseFilter(); andFilter.and(new EqualsFilter(uniqueIdAttributeName, uniqueId)); final String searchFilter = andFilter.encode(); @SuppressWarnings("unchecked") final List<BasicUser> results = ldapOperations.search("", searchFilter, basicUserAttributeMapper); return DataAccessUtils.uniqueResult(results); } @Override public Set<BasicUser> searchForUserByName(String name) { final List<String> nameParts = getNameParts(name); //Nothing useful to search on return an empty set if (nameParts.isEmpty()) { return Collections.emptySet(); } final AndFilter andFilter = createBaseFilter(); final OrFilter orFilter = new OrFilter(); final String namePartZero = nameParts.get(0); if (nameParts.size() == 1) { orFilter.or(new LikeFilter(firstNameAttributeName, namePartZero + "*")); orFilter.or(new LikeFilter(lastNameAttributeName, namePartZero + "*")); orFilter.or(new LikeFilter(displayNameAttributeName, namePartZero + "*")); } else { final AndFilter firstLastFilter = new AndFilter(); firstLastFilter.and(new LikeFilter(firstNameAttributeName, namePartZero + "*")); firstLastFilter.and(new LikeFilter(lastNameAttributeName, nameParts.get(nameParts.size() - 1) + "*")); orFilter.or(firstLastFilter); final String displayNameSearch = NAME_SPLIT.matcher(name.trim()).replaceAll("*") + "*"; orFilter.or(new LikeFilter(displayNameAttributeName, displayNameSearch)); } andFilter.and(orFilter); final String searchFilter = andFilter.encode(); @SuppressWarnings("unchecked") final List<BasicUser> results = ldapOperations.search("", searchFilter, basicUserAttributeMapper); return Collections.unmodifiableSet(new LinkedHashSet<BasicUser>(results)); } @Override public Set<BasicUser> searchForUserByEmail(String email) { email = StringUtils.trimToNull(email); if (email == null) { return Collections.emptySet(); } final AndFilter andFilter = createBaseFilter(); andFilter.and(new LikeFilter(mailAttributeName, email + "*")); final String searchFilter = andFilter.encode(); @SuppressWarnings("unchecked") final List<BasicUser> results = ldapOperations.search("", searchFilter, basicUserAttributeMapper); return Collections.unmodifiableSet(new LinkedHashSet<BasicUser>(results)); } protected AndFilter createBaseFilter() { final AndFilter andFilter = new AndFilter(); andFilter.and(new EqualsFilter("objectclass", "person")); return andFilter; } protected List<String> getNameParts(String name) { final String[] nameParts = NAME_SPLIT.split(name); final List<String> usefulNameParts = new ArrayList<String>(nameParts.length); for (String namePart : nameParts) { namePart = StringUtils.trimToNull(namePart); if (namePart != null) { usefulNameParts.add(namePart); } } return usefulNameParts; } private class BasicUserAttributeMapper implements AttributesMapper { @Override public Object mapFromAttributes(Attributes attributes) throws NamingException { final String uniqueId = getAttribute(uniqueIdAttributeName, attributes); if (uniqueId == null) { throw new IncorrectResultSizeDataAccessException("'" + uniqueIdAttributeName + "' is a required attribute", 1, 0); } String primaryEmail = null; final Builder<String> additionalEmailsBuilder = ImmutableSet.builder(); final Attribute emailAddressAttr = attributes.get(mailAttributeName); if (emailAddressAttr != null) { for (final NamingEnumeration<?> allEmailsEnum = emailAddressAttr.getAll(); allEmailsEnum.hasMore(); ) { final Object email = allEmailsEnum.next(); if (email == null) { continue; } final String emailStr = email.toString(); if (primaryEmail == null) { primaryEmail = emailStr; } else { additionalEmailsBuilder.add(emailStr); } } } final String displayName = getAttribute(displayNameAttributeName, attributes); return new BasicUserImpl(uniqueId, primaryEmail, displayName, additionalEmailsBuilder.build()); } private String getAttribute(String attributeName, Attributes attributes) throws NamingException { final Attribute attrValue = attributes.get(attributeName); if (attrValue == null) { return null; } final Object value = attrValue.get(); return value.toString(); } } }