001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.security.spi.impl.ldap;
018:
019: import javax.naming.Name;
020: import javax.naming.NameParser;
021: import javax.naming.NamingEnumeration;
022: import javax.naming.NamingException;
023: import javax.naming.directory.DirContext;
024: import javax.naming.directory.SearchControls;
025: import javax.naming.directory.SearchResult;
026: import javax.naming.ldap.LdapContext;
027:
028: import org.apache.commons.lang.StringUtils;
029: import org.apache.commons.logging.Log;
030: import org.apache.commons.logging.LogFactory;
031: import org.apache.jetspeed.security.InvalidDnException;
032: import org.apache.jetspeed.security.InvalidPasswordException;
033: import org.apache.jetspeed.security.InvalidUidException;
034: import org.apache.jetspeed.security.SecurityException;
035:
036: /**
037: * <p>
038: * Abstract ldap dao.
039: * </p>
040: *
041: * @author <a href="mailto:mike.long@dataline.com">Mike Long </a>, <a
042: * href="mailto:dlestrat@apache.org">David Le Strat</a>
043: */
044: public abstract class AbstractLdapDao {
045:
046: private static final Log logger = LogFactory
047: .getLog(AbstractLdapDao.class);
048:
049: /** The ldap binding configuration. */
050: private LdapBindingConfig ldapBindingConfig = null;
051:
052: /** Reference to remote server context */
053: protected LdapContext ctx;
054:
055: /**
056: * <p>
057: * Default constructor.
058: * </p>
059: */
060: public AbstractLdapDao() {
061: throw new UnsupportedOperationException(
062: "Must be instantiated with LDAP binding configuration.");
063: }
064:
065: /**
066: * <p>
067: * Initializes the dao.
068: * </p>
069: *
070: * @param ldapConfig Holds the ldap configuration.
071: * @throws SecurityException
072: */
073: public AbstractLdapDao(LdapBindingConfig ldapConfig)
074: throws SecurityException {
075: this .ldapBindingConfig = ldapConfig;
076: bindToServer(ldapConfig.getRootDn(), ldapConfig
077: .getRootPassword());
078: }
079:
080: /**
081: * <p>
082: * Binds to the ldap server.
083: * </p>
084: *
085: * @param rootDn
086: * @param rootPassword
087: * @throws SecurityException
088: */
089: protected void bindToServer(String rootDn, String rootPassword)
090: throws SecurityException {
091: if (ctx == null) {
092: validateDn(rootDn);
093: validatePassword(rootPassword);
094:
095: ctx = LdapContextProxy.createProxy(ldapBindingConfig);
096: }
097: }
098:
099: /**
100: * <p>
101: * Gets the sub context name.
102: * </p>
103: *
104: * @param dn The domain name.
105: * @return The sub context name.
106: * @throws NamingException
107: */
108: protected String getSubcontextName(final String dn)
109: throws NamingException {
110: NameParser parser = ctx.getNameParser("");
111: Name name = parser.parse(dn);
112: String rootStr = ctx.getNameInNamespace();
113: Name root = parser.parse(rootStr);
114:
115: if (name.startsWith(root)) {
116: Name rname = name.getSuffix(root.size());
117:
118: return rname.toString();
119: }
120:
121: return dn;
122: }
123:
124: /**
125: * <p>
126: * Validate the domain name.
127: * </p>
128: *
129: * @param dn The domain name.
130: */
131: protected void validateDn(final String dn) throws SecurityException {
132: if (StringUtils.isEmpty(dn)) {
133: throw new InvalidDnException();
134: }
135: }
136:
137: /**
138: * <p>
139: * Valiate the users password.
140: * </p>
141: *
142: * @param password The user.
143: */
144: protected void validatePassword(final String password)
145: throws SecurityException {
146: if (StringUtils.isEmpty(password)) {
147: throw new InvalidPasswordException();
148: }
149: }
150:
151: /**
152: * @return The factors that determine the scope of the search and what gets returned as a result
153: * of the search.
154: */
155: protected SearchControls setSearchControls() {
156: SearchControls controls = new SearchControls();
157: controls.setReturningAttributes(getKnownAttributes());
158: controls.setSearchScope(SearchControls.SUBTREE_SCOPE);
159: controls.setReturningObjFlag(true);
160:
161: return controls;
162: }
163:
164: /**
165: * <p>
166: * Searches the LDAP server for the user with the specified userid (uid attribute).
167: * </p>
168: *
169: * @return the user's DN
170: */
171: public String lookupByUid(final String uid)
172: throws SecurityException {
173: validateUid(uid);
174:
175: try {
176: SearchControls cons = setSearchControls();
177: NamingEnumeration searchResults = searchByWildcardedUid(
178: uid, cons);
179:
180: return getFirstDnForUid(searchResults);
181: } catch (NamingException e) {
182: throw new SecurityException(e);
183: }
184: }
185:
186: /**
187: * <p>
188: * Gets the first matching user for the given uid.
189: * </p>
190: *
191: * @param searchResults The {@link NamingEnumeration}.
192: * @return the user's DN of the first use in the list. Null if no users were found.
193: * @throws NamingException Throws a {@link NamingException}.
194: */
195: private String getFirstDnForUid(NamingEnumeration searchResults)
196: throws NamingException {
197: String userDn = null;
198: while ((null != searchResults) && searchResults.hasMore()) {
199: SearchResult searchResult = (SearchResult) searchResults
200: .next();
201: userDn = searchResult.getName();
202: String searchDomain = getSearchDomain();
203: if (searchDomain.length() > 0) {
204: userDn += ","
205: + StringUtils.replace(searchDomain, ","
206: + getRootContext(), "");
207: }
208: }
209: return userDn;
210: }
211:
212: /**
213: * <p>
214: * Validate the uid.
215: * </p>
216: *
217: * @param uid The uid.
218: */
219: protected void validateUid(String uid) throws SecurityException {
220: String pattern = ".*\\(.*|.*\\[.*|.*\\{.*|.*\\\\.*|.*\\^.*|.*\\$.*|.*\\|.*|.*\\).*|.*\\?.*|.*\\*.*|.*\\+.*|.*\\..*";
221: if (StringUtils.isEmpty(uid) || uid.matches(pattern)) {
222: throw new InvalidUidException();
223: }
224: }
225:
226: /**
227: * <p>
228: * Search uid by wild card.
229: * </p>
230: *
231: * @param filter The filter.
232: * @param cons The {@link SearchControls}
233: * @return The {@link NamingEnumeration}
234: * @throws NamingException Throws a {@link NamingEnumeration}.
235: */
236: protected NamingEnumeration searchByWildcardedUid(
237: final String filter, SearchControls cons)
238: throws NamingException {
239: // usa a template method to use users/groups/roles
240: String query = "";
241: if (StringUtils.isEmpty(getSearchSuffix())) {
242: query = "(" + getEntryPrefix() + "="
243: + (StringUtils.isEmpty(filter) ? "*" : filter)
244: + ")";
245: } else {
246: query = "(&(" + getEntryPrefix() + "="
247: + (StringUtils.isEmpty(filter) ? "*" : filter)
248: + ")" + getSearchSuffix() + ")";
249: }
250: logger.debug("searchByWildCardedUid = " + query);
251:
252: cons.setSearchScope(getSearchScope());
253: //TODO: added this here for OpenLDAP (when users are stored in ou=People,o=evenSeas)
254: String searchBase = StringUtils.replace(getSearchDomain(), ","
255: + getRootContext(), "");
256: NamingEnumeration results = ((DirContext) ctx).search(
257: searchBase, query, cons);
258:
259: return results;
260: }
261:
262: /**
263: * <p>
264: * Search uid by wild card.
265: * </p>
266: *
267: * @param filter The filter.
268: * @param cons The {@link SearchControls}
269: * @return The {@link NamingEnumeration}
270: * @throws NamingException Throws a {@link NamingEnumeration}.
271: */
272: protected NamingEnumeration searchGroupByWildcardedUid(
273: final String filter, SearchControls cons)
274: throws NamingException {
275: // usa a template method to use users/groups/roles
276: String query = "";
277: if (StringUtils.isEmpty(getGroupFilter())) {
278: query = "(" + getGroupIdAttribute() + "="
279: + (StringUtils.isEmpty(filter) ? "*" : filter)
280: + ")";
281: } else {
282: query = "(&(" + getGroupIdAttribute() + "="
283: + (StringUtils.isEmpty(filter) ? "*" : filter)
284: + ")" + getGroupFilter() + ")";
285: }
286:
287: String searchBase = "";
288: if (!StringUtils.isEmpty(getGroupFilterBase()))
289: searchBase += getGroupFilterBase();
290: cons.setSearchScope(getSearchScope());
291: NamingEnumeration results = ((DirContext) ctx).search(
292: searchBase, query, cons);
293:
294: return results;
295: }
296:
297: /**
298: * <p>
299: * Search uid by wild card.
300: * </p>
301: *
302: * @param filter The filter.
303: * @param cons The {@link SearchControls}
304: * @return The {@link NamingEnumeration}
305: * @throws NamingException Throws a {@link NamingEnumeration}.
306: */
307: protected NamingEnumeration searchRoleByWildcardedUid(
308: final String filter, SearchControls cons)
309: throws NamingException {
310: String query = "";
311: if (StringUtils.isEmpty(getRoleFilter())) {
312: query = "(" + getRoleIdAttribute() + "="
313: + (StringUtils.isEmpty(filter) ? "*" : filter)
314: + ")";
315: } else {
316: query = "(&(" + getRoleIdAttribute() + "="
317: + (StringUtils.isEmpty(filter) ? "*" : filter)
318: + ")" + getRoleFilter() + ")";
319: }
320:
321: String searchBase = "";
322: if (!StringUtils.isEmpty(getRoleFilterBase()))
323: searchBase += getRoleFilterBase();
324: cons.setSearchScope(getSearchScope());
325: NamingEnumeration results = ((DirContext) ctx).search(
326: searchBase, query, cons);
327:
328: return results;
329: }
330:
331: /**
332: * <p>
333: * Returns the default Group suffix dn.
334: * </p>
335: *
336: * @return The defaultDnSuffix.
337: */
338: protected String getGroupFilterBase() {
339: return this .ldapBindingConfig.getGroupFilterBase();
340: }
341:
342: /**
343: * <p>
344: * Returns the default Group suffix dn.
345: * </p>
346: *
347: * @return The defaultDnSuffix.
348: */
349: protected String[] getGroupObjectClasses() {
350: return this .ldapBindingConfig.getGroupObjectClasses();
351: }
352:
353: /**
354: * <p>
355: * Returns the default Group suffix dn.
356: * </p>
357: *
358: * @return The defaultDnSuffix.
359: */
360: protected String getRoleFilterBase() {
361: return this .ldapBindingConfig.getRoleFilterBase();
362: }
363:
364: /**
365: * <p>
366: * Returns the default Group suffix dn.
367: * </p>
368: *
369: * @return The defaultDnSuffix.
370: */
371: protected String[] getRoleObjectClasses() {
372: return this .ldapBindingConfig.getRoleObjectClasses();
373: }
374:
375: /**
376: * <p>
377: * Returns the default Group suffix dn.
378: * </p>
379: *
380: * @return The defaultDnSuffix.
381: */
382: protected String getUserFilterBase() {
383: return this .ldapBindingConfig.getUserFilterBase();
384: }
385:
386: /**
387: * <p>
388: * Returns the default Group suffix dn.
389: * </p>
390: *
391: * @return The defaultDnSuffix.
392: */
393: protected String getGroupFilter() {
394: return this .ldapBindingConfig.getGroupFilter();
395: }
396:
397: /**
398: * <p>
399: * Returns the default Group suffix dn.
400: * </p>
401: *
402: * @return The defaultDnSuffix.
403: */
404: protected String getRoleFilter() {
405: return this .ldapBindingConfig.getRoleFilter();
406: }
407:
408: /**
409: * <p>
410: * Returns the root context.
411: * </p>
412: *
413: * @return The root context.
414: */
415: protected String getRootContext() {
416: return this .ldapBindingConfig.getRootContext();
417: }
418:
419: /**
420: * <p>
421: * A template method that returns the LDAP entry prefix of the concrete DAO.
422: * </p>
423: *
424: * TODO : this should be in spring config
425: *
426: * @return a String containing the LDAP entry prefix name.
427: */
428: protected abstract String getEntryPrefix();
429:
430: /**
431: * <p>
432: * A template method that returns the LDAP entry prefix of the concrete DAO.
433: * </p>
434: *
435: * TODO : this should be in spring config
436: *
437: * @return a String containing the LDAP entry prefix name.
438: */
439: protected abstract String getSearchSuffix();
440:
441: /**
442: * <p>
443: * The domain in wich to perform a search
444: * </p>
445: *
446: * TODO : this should be in spring config
447: *
448: * @return a String containing the LDAP entry prefix name.
449: */
450: protected abstract String getSearchDomain();
451:
452: protected String getUserFilter() {
453: return this .ldapBindingConfig.getUserFilter();
454: }
455:
456: protected String[] getUserObjectClasses() {
457: return this .ldapBindingConfig.getUserObjectClasses();
458: }
459:
460: protected String getGroupMembershipAttribute() {
461: return this .ldapBindingConfig.getGroupMembershipAttributes();
462: }
463:
464: protected String getUserGroupMembershipAttribute() {
465: return this .ldapBindingConfig
466: .getUserGroupMembershipAttributes();
467: }
468:
469: protected String getGroupMembershipForRoleAttribute() {
470: return this .ldapBindingConfig
471: .getGroupMembershipForRoleAttributes();
472: }
473:
474: protected String getRoleGroupMembershipForRoleAttribute() {
475: return this .ldapBindingConfig
476: .getRoleGroupMembershipForRoleAttributes();
477: }
478:
479: protected String getRoleMembershipAttribute() {
480: return this .ldapBindingConfig.getRoleMembershipAttributes();
481: }
482:
483: protected String getUserRoleMembershipAttribute() {
484: return this .ldapBindingConfig.getUserRoleMembershipAttributes();
485: }
486:
487: protected String getRoleIdAttribute() {
488: return this .ldapBindingConfig.getRoleIdAttribute();
489: }
490:
491: protected String getGroupIdAttribute() {
492: return this .ldapBindingConfig.getGroupIdAttribute();
493: }
494:
495: protected String getUserIdAttribute() {
496: return this .ldapBindingConfig.getUserIdAttribute();
497: }
498:
499: protected String getUidAttribute() {
500: return this .ldapBindingConfig.getUidAttribute();
501: }
502:
503: protected int getSearchScope() {
504: return Integer.parseInt(this .ldapBindingConfig
505: .getMemberShipSearchScope());
506: }
507:
508: protected String getRoleUidAttribute() {
509: return this .ldapBindingConfig.getRoleUidAttribute();
510: }
511:
512: protected String getGroupUidAttribute() {
513: return this .ldapBindingConfig.getGroupUidAttribute();
514: }
515:
516: protected String getUserUidAttribute() {
517: return this .ldapBindingConfig.getUserUidAttribute();
518: }
519:
520: protected String getGroupObjectRequiredAttributeClasses() {
521: return this .ldapBindingConfig
522: .getGroupObjectRequiredAttributeClasses();
523: }
524:
525: protected String getRoleObjectRequiredAttributeClasses() {
526: return this .ldapBindingConfig
527: .getRoleObjectRequiredAttributeClasses();
528: }
529:
530: protected String[] getUserAttributes() {
531: return this .ldapBindingConfig.getUserAttributes();
532: }
533:
534: protected String[] getGroupAttributes() {
535: return this .ldapBindingConfig.getGroupAttributes();
536: }
537:
538: protected String[] getRoleAttributes() {
539: return this .ldapBindingConfig.getRoleAttributes();
540: }
541:
542: protected String getUserPasswordAttribute() {
543: return this .ldapBindingConfig.getUserPasswordAttribute();
544: }
545:
546: protected String[] getKnownAttributes() {
547: return this .ldapBindingConfig.getKnownAttributes();
548: }
549:
550: protected abstract String[] getObjectClasses();
551:
552: protected abstract String[] getAttributes();
553: }
|