/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. 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 * * 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 com.esri.gpt.framework.security.identity.ldap; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.naming.LimitExceededException; import javax.naming.NameNotFoundException; import javax.naming.NamingEnumeration; import javax.naming.NamingException; import javax.naming.PartialResultException; import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.directory.DirContext; import javax.naming.directory.SearchControls; import javax.naming.directory.SearchResult; import javax.naming.ldap.BasicControl; import javax.naming.ldap.Control; import javax.naming.ldap.LdapContext; import com.esri.gpt.framework.collection.StringSet; import com.esri.gpt.framework.security.principal.Group; import com.esri.gpt.framework.security.principal.Groups; import com.esri.gpt.framework.security.principal.User; import com.esri.gpt.framework.security.principal.UserAttribute; import com.esri.gpt.framework.security.principal.UserAttributeMap; import com.esri.gpt.framework.security.principal.Users; import com.esri.gpt.framework.util.LogUtil; import com.esri.gpt.framework.util.Val; /** * Handles functionality related to querying an LDAP identity store. */ public class LdapQueryFunctions extends LdapFunctions { // class variables ============================================================= // instance variables ========================================================== // constructors ================================================================ /** Default constructor. */ protected LdapQueryFunctions() { super(); } /** * Construct with a supplied configuration. * @param configuration the configuration */ protected LdapQueryFunctions(LdapConfiguration configuration) { super(configuration); } // properties ================================================================== // methods ===================================================================== /** * Appends attribute values to a map (keyed on attribute id). * @param attributes the attributes to append (from) * @param values the map of values to populate (to) * @param stringsOnly if true, only attributes values of type * String will be appended * @throws NamingException if an exception occurs */ protected void appendAttributeValues(Attributes attributes, Map<String,Object> values, boolean stringsOnly) throws NamingException { NamingEnumeration<?> enAttr = null; try { if (attributes != null) { enAttr = attributes.getAll(); while (enAttr.hasMore()) { Object oAttr = enAttr.next(); if (oAttr instanceof Attribute) { Attribute attr = (Attribute)oAttr; String sId = attr.getID(); Object oVal = attr.get(); if (!stringsOnly || (oVal instanceof String)) { values.put(sId,oVal); } else if (stringsOnly && (oVal == null)) { //values.put(sId,""); } //System.err.println(sId+"="+oVal+" cl="+oVal.getClass().getName()); } } enAttr.close(); } }catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } finally { closeEnumeration(enAttr); } } /** * Appends a collection of sub-string attribute values to a list. * <br/>The sub-attributes are determined by attribute.getAll(). * <br/>Only sub-attributes of type String will be appended. * @param attribute the attribute containing values to append (from) * @param values the list of values to populate (to) * @throws NamingException if an exception occurs */ protected void appendSubStringValues(Attribute attribute, StringSet values) throws NamingException { NamingEnumeration<?> enAttr = null; try { if (attribute != null) { enAttr = attribute.getAll(); while (enAttr.hasMore()) { Object oAttr = enAttr.next(); if (oAttr instanceof String) { values.add((String)oAttr); } } } } catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } finally { closeEnumeration(enAttr); } } /** * Determines group membership. * <br/>This method does not use the dynamic group member attribute. * <br/>This method doesn't work in all cases and is not used. * @param memberDN the distinguished name of the member * @param groupDN the distinguished name of the group * @return true if the member belongs to the group * @throws NamingException if an exception occurs */ private boolean isGroupMember(DirContext dirContext, String memberDN, String groupDN) throws NamingException { boolean bIsMember = false; NamingEnumeration<SearchResult> enSearch = null; memberDN = Val.chkStr(memberDN); groupDN = Val.chkStr(groupDN); if ((memberDN.length() > 0) && (groupDN.length() > 0)) { try { String sFilter = getConfiguration().getGroupProperties().returnGroupMemberSearchFilter(memberDN); SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.OBJECT_SCOPE); enSearch = dirContext.search(groupDN,sFilter,controls); bIsMember = enSearch.hasMore(); } catch (NamingException e) { String sMsg = e.getMessage()+" groupDN:"+groupDN; throw new NamingException(sMsg); } finally { closeEnumeration(enSearch); } } return bIsMember; } /** * Reads the attribute values associated with an attribute name. * @param dirContext the directory context * @param attrubuteName attribute name. * @param objectDN the distinguished name of the object * @return the list attribute values (strings only are returned) * @throws NamingException if an exception occurs */ protected StringSet readAttribute(DirContext dirContext, String objectDN, String attrubuteName) throws NamingException { StringSet values = new StringSet(); try{ if ((objectDN.length() > 0) && (attrubuteName.length() > 0)) { String[] aReturn = new String[1]; aReturn[0] = attrubuteName; try{ Attributes attributes = dirContext.getAttributes(objectDN,aReturn); if (attributes != null) { appendSubStringValues(attributes.get(attrubuteName),values); } }catch(NameNotFoundException nnfe){ LogUtil.getLogger().finer(nnfe.toString()); } } } catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } return values; } /** * Reads directory object attributes into a HashMap (keyed on attribute id). * @param objectDN the distinguished name of the object * @param stringsOnly if true, consider strings only * @return the attribute HashMap * @throws NamingException if an exception occurs */ protected Map<String,Object> readAttributes(DirContext dirContext, String objectDN, boolean stringsOnly) throws NamingException { Map<String,Object> map = new HashMap<String,Object>(); Attributes attributes = dirContext.getAttributes(objectDN); appendAttributeValues(attributes,map,stringsOnly); return map; } /** * Reads group member name strings into a list. * @param dirContext the directory context * @param groupDN the distinguished name of the group * @return the list of group member strings * @throws NamingException if an exception occurs */ protected StringSet readGroupMembers(DirContext dirContext, String groupDN) throws NamingException { StringSet members = new StringSet(); groupDN = Val.chkStr(groupDN); String sDynamic = getConfiguration().getGroupProperties().getGroupDynamicMembersAttribute(); if (groupDN.length() > 0) { if (sDynamic.length() > 0) { members = readAttribute(dirContext,groupDN,sDynamic); } else { Attributes attributes = dirContext.getAttributes(groupDN); if (attributes != null) { String sMemberTag = getConfiguration().getGroupProperties().getGroupMemberAttribute(); appendSubStringValues(attributes.get(sMemberTag),members); } } } return members; } /** * Retrieves this display name for a user. * @param dirContext the directory context * @param userDN the distinguished name for the user * @return the user display name * @throws NamingException */ protected String readUserDisplayName(DirContext dirContext, String userDN) throws NamingException { userDN = Val.chkStr(userDN); String sDisplayName = userDN; String sDisplayAttr = getConfiguration().getUserProperties().getUserDisplayNameAttribute(); if ((userDN.length() > 0) && (sDisplayAttr.length() > 0)) { //try { StringSet ss = readAttribute(dirContext,userDN,sDisplayAttr); if (ss.size() > 0) { sDisplayName = ss.iterator().next(); } //} catch (Exception e) { // sDisplayName = userDN; //System.err.println("Error reading user display name for distinguished name: "+userDN); //e.printStackTrace(System.err); //} } return sDisplayName; } /** * Reads the groups to which a user belongs. * @param dirContext the directory context * @param user the subject user * @throws NamingException if an LDAP naming exception occurs */ protected void readUserGroups(DirContext dirContext, User user) throws NamingException { NamingEnumeration<SearchResult> enSearch = null; try { String sUserDN = user.getDistinguishedName(); LdapGroupProperties props = getConfiguration().getGroupProperties(); String sDynamicAttribute = props.getGroupDynamicMemberAttribute(); String sNameAttribute = props.getGroupDisplayNameAttribute(); String recursiveControlId = ""; if (sDynamicAttribute.startsWith("controlid=")) { recursiveControlId = Val.chkStr(sDynamicAttribute.substring(10)); sDynamicAttribute = ""; } if (sUserDN.equals("*") || (sDynamicAttribute.length() == 0)) { // read group membership based upon a search filter String sBaseDN = props.getGroupSearchDIT(); String sFilter = props.returnGroupMemberSearchFilter(sUserDN); if ((sUserDN.length() > 0) && (sFilter.length() > 0)) { // the is to handle an issue with activeDirectory recursion if (sUserDN.equals("*")) { sFilter = sFilter.replace("member:1.2.840.113556.1.4.1941:=","member="); } // supply a recursion control such as Oracle's CONNECT_BY (2.16.840.1.113894.1.8.3) if (!sUserDN.equals("*") && (recursiveControlId.length() > 0)) { if (dirContext instanceof LdapContext) { LdapContext ldapContext = (LdapContext)dirContext; Control[] aControls = ldapContext.getRequestControls(); Control recursiveControl = new BasicControl(recursiveControlId); List<Control> lControls = new ArrayList<Control>(); if (aControls != null) { for (Control ctl: aControls) { lControls.add(ctl); } } lControls.add(recursiveControl); ldapContext.setRequestControls(lControls.toArray(new Control[0])); } } SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.SUBTREE_SCOPE); if (sNameAttribute.length() > 0) { String[] aReturn = new String[1]; aReturn[0] = sNameAttribute; controls.setReturningAttributes(aReturn); } enSearch = dirContext.search(sBaseDN,sFilter,controls); try { while (enSearch.hasMore()) { SearchResult result = (SearchResult)enSearch.next(); String sDN = buildFullDN(result.getName(),sBaseDN); if (sDN.length() > 0) { String sName = ""; if (sNameAttribute.length() > 0) { Attribute attrName = result.getAttributes().get(sNameAttribute); if ((attrName != null) && (attrName.size() > 0)) { sName = Val.chkStr(attrName.get(0).toString()); } } Group group = new Group(); group.setDistinguishedName(sDN); group.setKey(group.getDistinguishedName()); group.setName(sName); user.getGroups().add(group); } } } catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } } } else { // read group membership based upon a dynamic attribute StringSet groupDNs = readAttribute(dirContext,sUserDN,sDynamicAttribute); for (String sDN: groupDNs) { sDN = sDN.toLowerCase(); if (sDN.length() > 0) { String sName = ""; if (sNameAttribute.length() > 0) { StringSet ss = readAttribute(dirContext,sDN,sNameAttribute); if (ss.size() > 0) { sName = ss.iterator().next(); } } Group group = new Group(sDN); group.setDistinguishedName(sDN); group.setName(sName); user.getGroups().add(group); } } } } finally { closeEnumeration(enSearch); } } /** * Retrieves this username attribute for a user. * @param dirContext the directory context * @param userDN the distinguished name for the user * @return the username * @throws NamingException if the username attribute does not exist */ protected String readUsername(DirContext dirContext, String userDN) throws NamingException { userDN = Val.chkStr(userDN); String sName = userDN; LdapNameMapping nameMap = getConfiguration().getUserProperties().getUserProfileMapping(); String sAttrName = nameMap.findLdapName(UserAttributeMap.TAG_USER_NAME); if (sAttrName.length() == 0) sAttrName = "cn"; if ((userDN.length() > 0) && (sAttrName.length() > 0)) { //try { StringSet ss = readAttribute(dirContext,userDN,sAttrName); if (ss.size() > 0) { sName = ss.iterator().next(); } else if (!sAttrName.equals("cn")) { ss = readAttribute(dirContext,userDN,"cn"); if (ss.size() > 0) { sName = ss.iterator().next(); } } //} catch (Exception e) { // sName = userDN; //System.err.println("Error reading username for distinguished name: "+userDN); //e.printStackTrace(System.err); //} } return sName; } /** * Reads the profile attributes for a user. * @param dirContext the directory context * @param user the subject user * @throws NamingException if an LDAP naming exception occurs */ protected void readUserProfile(DirContext dirContext, User user) throws NamingException { LdapNameMapping nameMap = getConfiguration().getUserProperties().getUserProfileMapping(); UserAttributeMap userProf = user.getProfile(); UserAttributeMap configured = getConfiguration().getIdentityConfiguration().getUserAttributeMap(); /* There were some issues with the initial integration of * Apache's LDAP implementation. The section below ensures that a user's * profile contains all configured attributes, even if they do not exist on * the LDAP side. */ boolean bEnsureAllAttributes = true; if (bEnsureAllAttributes) { for (UserAttribute attr: configured.values()) { if (!userProf.containsKey(attr.getKey())) { userProf.set(attr.getKey(),""); } } } String sUserDN = user.getDistinguishedName(); if (sUserDN.length() > 0) { // read current LDAP attribute values NamingEnumeration<?> enAttr = null; try { Attributes attributes = dirContext.getAttributes(sUserDN); if (attributes != null) { enAttr = attributes.getAll(); while (enAttr.hasMore()) { Object oAttr = enAttr.next(); if (oAttr instanceof Attribute) { Attribute attr = (Attribute)oAttr; String sLdapKey = attr.getID(); Object oVal = attr.get(); // set the corresponding application user attribute String sAppKey = Val.chkStr(nameMap.findApplicationName(sLdapKey)); if ((sAppKey.length() > 0) && configured.containsKey(sAppKey)) { if (oVal instanceof String) { userProf.set(sAppKey,(String)oVal); } else if (oVal == null) { userProf.set(sAppKey,""); } } } } enAttr.close(); } } finally { closeEnumeration(enAttr); } } } /** * Builds list of ldap users matching filter. * @param dirContext the directory context * @param filter the user search filter for ldap * @return the list of users matching filter * @throws NamingException if an LDAP naming exception occurs */ protected Users readUsers(DirContext dirContext,String filter, String attributeName) throws NamingException{ Users users = new Users(); NamingEnumeration<SearchResult> enSearch = null; try{ LdapUserProperties userProps = getConfiguration().getUserProperties(); String sNameAttribute = userProps.getUserDisplayNameAttribute(); String sBaseDN = userProps.getUserSearchDIT(); String sFilter = userProps.returnUserLoginSearchFilter(filter); if(attributeName != null){ sFilter = userProps.returnUserNewRequestSearchFilter(filter, attributeName); } SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.SUBTREE_SCOPE); if (sNameAttribute.length() > 0) { String[] aReturn = new String[1]; aReturn[0] = sNameAttribute; controls.setReturningAttributes(aReturn); } enSearch = dirContext.search(sBaseDN,sFilter,controls); try { while (enSearch.hasMore()) { SearchResult result = (SearchResult)enSearch.next(); String sDN = buildFullDN(result.getName(),sBaseDN); if (sDN.length() > 0) { String sName = ""; if (sNameAttribute.length() > 0) { Attribute attrName = result.getAttributes().get(sNameAttribute); if ((attrName != null) && (attrName.size() > 0)) { sName = Val.chkStr(attrName.get(0).toString()); } } User user = new User(); user.setDistinguishedName(sDN); user.setKey(user.getDistinguishedName()); user.setName(sName); users.add(user); } } } catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } }finally { closeEnumeration(enSearch); } return users; } /** * Builds list of ldap groups matching filter. * @param dirContext the directory context * @param filter the group search filter for ldap * @return the list of groups matching filter * @throws NamingException if an LDAP naming exception occurs */ protected Groups readGroups(DirContext dirContext,String filter) throws NamingException{ Groups groups = new Groups(); NamingEnumeration<SearchResult> enSearch = null; try{ LdapGroupProperties groupProps = getConfiguration().getGroupProperties(); String sNameAttribute = groupProps.getGroupDisplayNameAttribute(); String sBaseDN = groupProps.getGroupSearchDIT(); String sFilter = groupProps.returnGroupNameSearchFilter(filter); SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.SUBTREE_SCOPE); if (sNameAttribute.length() > 0) { String[] aReturn = new String[1]; aReturn[0] = sNameAttribute; controls.setReturningAttributes(aReturn); } enSearch = dirContext.search(sBaseDN,sFilter,controls); try { while (enSearch.hasMore()) { SearchResult result = (SearchResult)enSearch.next(); String sDN = buildFullDN(result.getName(),sBaseDN); if (sDN.length() > 0) { String sName = ""; if (sNameAttribute.length() > 0) { Attribute attrName = result.getAttributes().get(sNameAttribute); if ((attrName != null) && (attrName.size() > 0)) { sName = Val.chkStr(attrName.get(0).toString()); } } Group group = new Group(); group.setDistinguishedName(sDN); group.setKey(group.getDistinguishedName()); group.setName(sName); groups.add(group); } } } catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } }finally { closeEnumeration(enSearch); } return groups; } /** * Returns a list of distinguished names resulting from a search. * <br/>The search is executed with SearchControls.SUBTREE_SCOPE. * @param dirContext the directory context * @param baseDN the baseBN for the search * @param filter the filter for the search * @return a collection of distinguished names * @throws NamingException if an exception occurs */ protected StringSet searchDNs(DirContext dirContext, String baseDN, String filter) throws NamingException { StringSet names = new StringSet(false,false,true); NamingEnumeration<SearchResult> enSearch = null; try { baseDN = Val.chkStr(baseDN); filter = Val.chkStr(filter); if (filter.length() > 0) { SearchControls controls = new SearchControls(); controls.setSearchScope(SearchControls.SUBTREE_SCOPE); enSearch = dirContext.search(baseDN,filter,controls); try { while (enSearch.hasMore()) { SearchResult result = (SearchResult)enSearch.next(); names.add(buildFullDN(result.getName(),baseDN)); } } catch (PartialResultException pre) { LogUtil.getLogger().finer(pre.toString()); } catch (LimitExceededException lee) { LogUtil.getLogger().finer(lee.toString()); } } } finally { closeEnumeration(enSearch); } return names; } }