View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.dao.impl;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.kuali.rice.coreservice.framework.parameter.ParameterService;
20  import org.kuali.rice.kim.api.identity.entity.Entity;
21  import org.kuali.rice.kim.api.identity.entity.EntityDefault;
22  import org.kuali.rice.kim.api.identity.principal.EntityNamePrincipalName;
23  import org.kuali.rice.kim.api.identity.principal.Principal;
24  import org.kuali.rice.kim.api.identity.privacy.EntityPrivacyPreferences;
25  import org.kuali.rice.kim.dao.LdapPrincipalDao;
26  import org.kuali.rice.kim.impl.identity.PersonImpl;
27  import org.kuali.rice.kim.util.Constants;
28  import org.springframework.ldap.SizeLimitExceededException;
29  import org.springframework.ldap.core.ContextMapper;
30  import org.springframework.ldap.core.ContextMapperCallbackHandler;
31  import org.springframework.ldap.core.DistinguishedName;
32  import org.springframework.ldap.core.LdapTemplate;
33  import org.springframework.ldap.filter.AndFilter;
34  import org.springframework.ldap.filter.LikeFilter;
35  import org.springframework.ldap.filter.NotFilter;
36  import org.springframework.ldap.filter.OrFilter;
37  
38  import javax.naming.directory.SearchControls;
39  import java.util.ArrayList;
40  import java.util.Arrays;
41  import java.util.HashMap;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.regex.Matcher;
45  import java.util.regex.Pattern;
46  
47  import static org.kuali.rice.core.util.BufferedLogger.*;
48  import static org.kuali.rice.kns.lookup.LookupUtils.getSearchResultsLimit;
49  
50  /**
51   * Integrated Data Access via LDAP to EDS. Provides implementation to interface method
52   * for using Spring-LDAP to communicate with EDS.
53   *
54   * @author Kuali Rice Team (rice.collab@kuali.org)
55   */
56  public class LdapPrincipalDaoImpl implements LdapPrincipalDao { 
57      private Constants kimConstants;
58      private LdapTemplate template;
59      private ParameterService parameterService;
60  
61      
62      private Map<String, ContextMapper> contextMappers;    
63      
64      public LdapPrincipalDaoImpl() {
65      }
66                              
67      /**
68       * In EDS, the principalId, principalName, and entityId will all be the same.
69       */
70      public Principal getPrincipal(String principalId) {
71          if (principalId == null) {
72              return null;
73          }
74          Map<String, Object> criteria = new HashMap();
75          criteria.put(getKimConstants().getKimLdapIdProperty(), principalId);
76          List<Principal> results = search(Principal.class, criteria);
77  
78          if (results.size() > 0) {
79              return results.get(0);
80          }
81  
82          return null;
83      }
84  
85      /**
86       * Assuming he principalId, principalName, and entityId will all be the same.
87       */
88      public Principal getPrincipalByName(String principalName) {
89          if (principalName == null) {
90              return null;
91          }
92          Map<String, Object> criteria = new HashMap();
93          criteria.put(getKimConstants().getKimLdapNameProperty(), principalName);
94          List<Principal> results = search(Principal.class, criteria);
95  
96          if (results.size() > 0) {
97              return results.get(0);
98          }
99          
100         return null;
101     }
102 
103     public <T> List<T> search(Class<T> type, Map<String, Object> criteria) {
104         AndFilter filter = new AndFilter();
105         
106         for (Map.Entry<String, Object> entry : criteria.entrySet()) {
107             //attempting to handle null values to prevent NPEs in this code.
108             if (entry.getValue() == null) {
109                 entry.setValue("null");
110             }
111             if (entry.getValue() instanceof Iterable) {
112                 OrFilter orFilter = new OrFilter();
113                 for (String value : (Iterable<String>) entry.getValue()) {
114                     if (value.startsWith("!")) {
115                         orFilter.or(new NotFilter(new LikeFilter(entry.getKey(), value.substring(1))));
116                     } else {
117                         orFilter.or(new LikeFilter(entry.getKey(), value));
118                     }
119                 }
120                 filter.and(orFilter);
121             }
122             else {
123                 if (((String)entry.getValue()).startsWith("!")) {
124                     filter.and(new NotFilter(new LikeFilter(entry.getKey(), ((String)entry.getValue()).substring(1))));
125                 } else {
126                     filter.and(new LikeFilter(entry.getKey(), (String) entry.getValue()));
127                 }
128             }
129         };
130         
131         info("Using filter ", filter);
132 
133         debug("Looking up mapper for ", type.getSimpleName());
134         final ContextMapper customMapper = contextMappers.get(type.getSimpleName());
135 
136         ContextMapperCallbackHandler callbackHandler = new CustomContextMapperCallbackHandler(customMapper);
137         
138         try {
139             getLdapTemplate().search(DistinguishedName.EMPTY_PATH, 
140                                      filter.encode(), 
141                                      getSearchControls(), callbackHandler);
142         }
143         catch (SizeLimitExceededException e) {
144             // Ignore this. We want to limit our results.
145         }
146 
147         return callbackHandler.getList();
148     }
149 
150     protected SearchControls getSearchControls() {
151         SearchControls retval = new SearchControls();
152         retval.setCountLimit(getSearchResultsLimit(PersonImpl.class).longValue());
153         retval.setSearchScope(SearchControls.SUBTREE_SCOPE);
154         return retval;
155     }
156 
157 	/**
158      * FIND entity objects based on the given criteria. 
159      * 
160      * @param entityId of user/person to grab entity information for
161      * @return {@link Entity}
162      */
163 	public Entity getEntity(String entityId) {
164 	    if (entityId == null) {
165 	        return null;
166 	    }
167         Map<String, Object> criteria = new HashMap();
168         criteria.put(getKimConstants().getKimLdapIdProperty(), entityId);
169 
170         List<Entity> results = search(Entity.class, criteria);
171 
172         debug("Got results from info lookup ", results, " with size ", results.size());
173 
174         if (results.size() > 0) {
175             return results.get(0);
176         }
177         
178         return null;
179     }
180 	
181 	/**
182 	 * Fetches full entity info, populated from EDS, based on the Entity's principal id
183 	 * @param principalId the principal id to look the entity up for
184 	 * @return the corresponding entity info
185 	 */
186 	public Entity getEntityByPrincipalId(String principalId) {
187 	    if (principalId == null) {
188 	        return null;
189 	    }
190 	   final Principal principal = getPrincipal(principalId);
191 	   if (principal != null && !StringUtils.isBlank(principal.getEntityId())) {
192 	       return getEntity(principal.getEntityId());
193 	   }
194 	   return null;
195 	}
196 
197 	public EntityDefault getEntityDefault(String entityId) {
198 	    if (entityId == null) {
199 	        return null;
200 	    }
201         Map<String, Object> criteria = new HashMap();
202         criteria.put(getKimConstants().getKimLdapIdProperty(), entityId);
203 
204         List<EntityDefault> results = search(EntityDefault.class, criteria);
205 
206         debug("Got results from info lookup ", results, " with size ", results.size());
207 
208         if (results.size() > 0) {
209             return results.get(0);
210         }
211         
212         return null;
213     }
214 
215     /**
216      * entityid and principalId are treated as the same.
217      * 
218      * @see #getEntityDefaultInfo(String)
219      */
220 	public EntityDefault getEntityDefaultByPrincipalId(String principalId) {
221         return getEntityDefault(principalId);
222     }
223 
224 	public EntityDefault getEntityDefaultByPrincipalName(String principalName) {
225         Map<String, Object> criteria = new HashMap();
226         criteria.put(getKimConstants().getKimLdapNameProperty(), principalName);
227 
228         List<EntityDefault> results = search(EntityDefault.class, criteria);
229         if (results.size() > 0) {
230             return results.get(0);
231         }
232         
233         return null;
234     }
235 
236 	public Entity getEntityByPrincipalName(String principalName) {
237         Map<String, Object> criteria = new HashMap();
238         criteria.put(getKimConstants().getKimLdapNameProperty(), principalName);
239 
240         List<Entity> results = search(Entity.class, criteria);
241         if (results.size() > 0) {
242             return results.get(0);
243         }
244         
245         return null;
246     }
247 
248 	public List<EntityDefault> lookupEntityDefault(Map<String,String> searchCriteria, boolean unbounded) {
249         List<EntityDefault> results = new ArrayList();
250         Map<String, Object> criteria = getLdapLookupCriteria(searchCriteria);
251         
252         results = search(EntityDefault.class, criteria);
253 
254         return results;
255     }
256 
257 	public List<String> lookupEntityIds(Map<String,String> searchCriteria) {
258         final List<String> results = new ArrayList<String>();
259         final Map<String, Object> criteria = getLdapLookupCriteria(searchCriteria);
260         
261         for (final Entity entity : search(Entity.class, criteria)) {
262             results.add(entity.getId());
263         }
264         
265         return results;
266     }
267     
268     /**
269      * Converts Kuali Lookup parameters into LDAP query parameters
270      * @param searchCriteria kuali lookup info
271      * @return {@link Map} of LDAP query info
272      */
273     protected Map<String, Object> getLdapLookupCriteria(Map<String, String> searchCriteria) {
274         Map<String, Object> criteria = new HashMap();
275         boolean hasTaxId = false;
276         
277         for (Map.Entry<String, String> criteriaEntry : searchCriteria.entrySet()) {
278             debug(String.format("Searching with criteria %s = %s", criteriaEntry.getKey(), criteriaEntry.getValue()));
279             String valueName = criteriaEntry.getKey();            
280             Object value = criteriaEntry.getValue();
281             if (!criteriaEntry.getValue().equals("*")) {
282                 valueName = String.format("%s.%s", criteriaEntry.getKey(), criteriaEntry.getValue());
283             }
284 
285             if (!value.equals("*") && isMapped(valueName)) {
286                 value = getLdapValue(valueName);
287                 debug(value, " mapped to valueName ", valueName);
288             }
289         
290             if (isMapped(criteriaEntry.getKey())) {
291                 debug(String.format("Setting attribute to (%s, %s)", 
292                                     getLdapAttribute(criteriaEntry.getKey()), 
293                                     value));
294                 final String key = getLdapAttribute(criteriaEntry.getKey());
295                 if (!criteria.containsKey(key)) {
296                     criteria.put(key, value);
297                 }
298             }
299             else if (criteriaEntry.getKey().equalsIgnoreCase(getKimConstants().getExternalIdProperty())) {
300                 criteria.put(getKimConstants().getKimLdapIdProperty(), value);
301             }
302             else if (criteriaEntry.getKey().equalsIgnoreCase(getKimConstants().getExternalIdTypeProperty()) 
303                      && value.toString().equals(getKimConstants().getTaxExternalIdTypeCode())) {
304                 hasTaxId = true;
305             }
306         }
307         return criteria;
308     }
309 
310 	public EntityPrivacyPreferences getEntityPrivacyPreferences(String entityId) {
311 	    if (entityId == null) {
312 	        return null;
313 	    }
314         Map<String, Object> criteria = new HashMap();
315         criteria.put(getKimConstants().getKimLdapIdProperty(), entityId);
316 
317         List<EntityPrivacyPreferences> results = search(EntityPrivacyPreferences.class, criteria);
318         if (results.size() > 0) {
319             return results.get(0);
320         }
321         
322         return null;
323     }
324 	
325     public Map<String, EntityNamePrincipalName> getDefaultNamesForPrincipalIds(List<String> principalIds) {
326         Map<String, Object> criteria = new HashMap();
327         Map<String, EntityNamePrincipalName> retval = new HashMap();
328         criteria.put(getKimConstants().getKimLdapIdProperty(), principalIds);
329 
330         List<EntityNamePrincipalName> results = search(EntityNamePrincipalName.class, criteria);
331 
332         for (EntityNamePrincipalName nameInfo : results) {
333             retval.put(nameInfo.getPrincipalName(), nameInfo);
334         }
335         return retval;
336     }
337 
338     public Map<String, EntityNamePrincipalName> getDefaultNamesForEntityIds(List<String> entityIds) {
339         return getDefaultNamesForPrincipalIds(entityIds);
340     }
341 
342     protected Matcher getKimAttributeMatcher(String kimAttribute) {
343         String mappedParamValue = getParameterService().getParameterValueAsString(getKimConstants().getParameterNamespaceCode(),
344                                                                                   getKimConstants().getParameterDetailTypeCode(),
345                                                                                   getKimConstants().getMappedParameterName());
346 
347         String regexStr = String.format("(%s|.*;%s)=([^=;]*).*", kimAttribute, kimAttribute);
348         debug("Matching KIM attribute with regex ", regexStr);
349         Matcher retval = Pattern.compile(regexStr).matcher(mappedParamValue);
350         
351         if (!retval.matches()) {
352             mappedParamValue = getParameterService().getParameterValueAsString(getKimConstants().getParameterNamespaceCode(),
353                                                                           getKimConstants().getParameterDetailTypeCode(),
354                                                                           getKimConstants().getMappedValuesName());
355             retval = Pattern.compile(regexStr).matcher(mappedParamValue);
356         }
357 
358         return retval;
359     }
360 
361     protected boolean isMapped(String kimAttribute) {
362         debug("Matching " + kimAttribute);
363         debug("Does ", kimAttribute, " match? ", getKimAttributeMatcher(kimAttribute).matches());
364         return getKimAttributeMatcher(kimAttribute).matches();
365     }
366 
367     protected String getLdapAttribute(String kimAttribute) {
368         Matcher matcher = getKimAttributeMatcher(kimAttribute);
369         debug("Does ", kimAttribute, " match? ", matcher.matches());
370         if (matcher.matches()) { 
371             return matcher.group(2);
372         } else {
373             return null;
374         }
375     }
376 
377     protected Object getLdapValue(String kimAttribute) {
378         Matcher matcher = getKimAttributeMatcher(kimAttribute);
379         debug("Does ", kimAttribute, " match? ", matcher.matches());
380         if (!matcher.matches()) {
381             return null;
382         }
383         String value = matcher.group(2);
384 
385         // If it's actually a list. It can only be a list if there are commas
386         if (value.contains(",")) {
387             return Arrays.asList(value.split(","));
388         }
389 
390         return value;
391     }
392 
393     public void setKimConstants(Constants constants) {
394         this.kimConstants = constants;
395     }
396 
397     public Constants getKimConstants() {
398         return kimConstants;
399     }
400 
401     public ParameterService getParameterService() {
402         return this.parameterService;
403     }
404 
405     public void setParameterService(ParameterService service) {
406         this.parameterService = service;
407     }
408 
409     public LdapTemplate getLdapTemplate() {
410         return template;
411     }
412 
413     public void setLdapTemplate(LdapTemplate template) {
414         this.template = template;
415     }
416     
417     public Map<String, ContextMapper> getContextMappers() {
418         return this.contextMappers;
419     }
420 
421     public void setContextMappers(final Map<String, ContextMapper> contextMappers) {
422         this.contextMappers = contextMappers;
423     }
424 
425     /**
426      * Overrides the existing {@link ContextMapperCallbackHandler} because we want to 
427      * intercede when there is invalid results from EDS.
428      * 
429      * @author Leo Przybylski (przybyls@arizona.edu)
430      */
431     private static final class CustomContextMapperCallbackHandler extends ContextMapperCallbackHandler {
432         public CustomContextMapperCallbackHandler(ContextMapper mapper) {
433             super(mapper);
434         }
435     }
436 }