001: /**
002: * Copyright (c) 2003-2007, David A. Czarnecki
003: * All rights reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions are met:
007: *
008: * Redistributions of source code must retain the above copyright notice, this list of conditions and the
009: * following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
011: * following disclaimer in the documentation and/or other materials provided with the distribution.
012: * Neither the name of "David A. Czarnecki" and "blojsom" nor the names of its contributors may be used to
013: * endorse or promote products derived from this software without specific prior written permission.
014: * Products derived from this software may not be called "blojsom", nor may "blojsom" appear in their name,
015: * without prior written permission of David A. Czarnecki.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
018: * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
019: * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020: * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
021: * EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
022: * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
023: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
025: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
026: * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
027: * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
028: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
029: * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
030: */package org.blojsom.authorization.ldap;
031:
032: import netscape.ldap.*;
033: import netscape.ldap.factory.JSSESocketFactory;
034: import org.apache.commons.logging.Log;
035: import org.apache.commons.logging.LogFactory;
036: import org.blojsom.ConfigurationException;
037: import org.blojsom.authorization.AuthorizationException;
038: import org.blojsom.authorization.database.DatabaseAuthorizationProvider;
039: import org.blojsom.blog.Blog;
040: import org.blojsom.util.BlojsomUtils;
041:
042: import javax.servlet.ServletConfig;
043: import java.util.Enumeration;
044: import java.util.Map;
045:
046: /**
047: * LDAPAuthorizationProvider
048: * <p></p>
049: * This implementation authenticates a user against an LDAP server. The user
050: * name must be the same as that of their LDAP user (uid). There are two ways
051: * to configure this in terms of the accepted users. The first is where only
052: * the blog owner can edit the blog. To use this technique, delete the
053: * authorization.properties file from the user's blog directory. The lack of
054: * this file tells the authorization logic to use the blog owner as the UID for
055: * LDAP authentication. The second way provides multiple user editing of a
056: * blog. This second way utilizes the authorization.properties file's user
057: * names (it ignores passwords and other data). Incoming authorization requests
058: * have the user name checked to see if it is listed in the
059: * authorization.properties file (indicating a user who is allowed to edit this
060: * blog). If it is in the list, this username is used as the LDAP UID. This
061: * class/implementation requires LDAP protocol version 3. You must set the
062: * configuration values defined by the BlojsomConstants:
063: * BLOG_LDAP_AUTHORIZATION_SERVER_IP, BLOG_LDAP_AUTHORIZATION_DN_IP, and
064: * BLOG_LDAP_AUTHORIZATION_PORT_IP (optional).
065: * <p></p>
066: * Note, this implementation currently requires the Mozilla LDAP Java SDK. See
067: * http://www.mozilla.org/directory/.
068: *
069: * @author David Czarnecki
070: * @author Christopher Bailey
071: * @version $Id: LDAPAuthorizationProvider.java,v 1.5 2007/01/17 01:15:46 czarneckid Exp $
072: * @since blojsom 3.0
073: */
074: public class LDAPAuthorizationProvider extends
075: DatabaseAuthorizationProvider {
076:
077: private static final String BLOG_LDAP_AUTHORIZATION_SERVER_IP = "blog-ldap-authorization-server";
078: private static final String BLOG_LDAP_AUTHORIZATION_PORT_IP = "blog-ldap-authorization-port";
079: private static final String BLOG_LDAP_AUTHORIZATION_DN_IP = "blog-ldap-authorization-dn";
080: private static final String BLOG_LDAP_AUTHORIZATION_UID_IP = "blog-ldap-authorization-uid";
081: private static final String BLOG_LDAP_AUTHORIZATION_BINDING_USER_IP = "blog-ldap-authorization-bindinguser";
082: private static final String BLOG_LDAP_AUTHORIZATION_BINDING_PASSWORD_IP = "blog-ldap-authorization-bindingpassword";
083: private static final String BLOG_LDAP_AUTHORIZATION_USE_SSL = "blog-ldap-authorization-use-ssl";
084:
085: private static final String UID_DEFAULT = "uid";
086:
087: private Log _logger = LogFactory
088: .getLog(LDAPAuthorizationProvider.class);
089: private String _ldapServer;
090: private int _ldapPort = 389;
091: private String _ldapDN;
092: private String _uidAttributeName = UID_DEFAULT;
093:
094: private String _bindingUser = null;
095: private String _bindingPassword = null;
096: private ServletConfig _servletConfig;
097: private boolean _useSSL = false;
098:
099: /**
100: * Default constructor
101: */
102: public LDAPAuthorizationProvider() {
103: }
104:
105: /**
106: * Set the {@link ServletConfig} for the fetcher to grab initialization parameters
107: *
108: * @param servletConfig {@link ServletConfig}
109: */
110: public void setServletConfig(ServletConfig servletConfig) {
111: _servletConfig = servletConfig;
112: }
113:
114: /**
115: * Initialization method for the authorization provider
116: *
117: * @throws ConfigurationException If there is an error initializing the provider
118: */
119: public void init() throws ConfigurationException {
120: super .init();
121:
122: _ldapServer = _servletConfig
123: .getInitParameter(BLOG_LDAP_AUTHORIZATION_SERVER_IP);
124: _ldapDN = _servletConfig
125: .getInitParameter(BLOG_LDAP_AUTHORIZATION_DN_IP);
126: String port = _servletConfig
127: .getInitParameter(BLOG_LDAP_AUTHORIZATION_PORT_IP);
128: if (!BlojsomUtils.checkNullOrBlank(_servletConfig
129: .getInitParameter(BLOG_LDAP_AUTHORIZATION_UID_IP))) {
130: _uidAttributeName = _servletConfig
131: .getInitParameter(BLOG_LDAP_AUTHORIZATION_UID_IP);
132: }
133:
134: if (!BlojsomUtils.checkNullOrBlank(_servletConfig
135: .getInitParameter(BLOG_LDAP_AUTHORIZATION_USE_SSL))) {
136: String bool = _servletConfig
137: .getInitParameter(BLOG_LDAP_AUTHORIZATION_USE_SSL);
138: _useSSL = Boolean.valueOf(bool).booleanValue();
139: }
140:
141: // We don't setup a credentions map here, because with LDAP, you can't
142: // obtain the user's passwords, you can only check/authenticate against
143: // the LDAP server. Instead, check each time in the authorize method.
144: if (BlojsomUtils.checkNullOrBlank(_ldapServer)) {
145: String msg = "No LDAP authorization server specified.";
146: if (_logger.isErrorEnabled()) {
147: _logger.error(msg);
148: }
149:
150: throw new ConfigurationException(msg);
151: }
152:
153: if (BlojsomUtils.checkNullOrBlank(_ldapDN)) {
154: String msg = "No LDAP authorization DN specified.";
155: if (_logger.isErrorEnabled()) {
156: _logger.error(msg);
157: }
158:
159: throw new ConfigurationException(msg);
160: }
161:
162: if (!BlojsomUtils.checkNullOrBlank(port)) {
163: try {
164: _ldapPort = Integer.valueOf(port).intValue();
165: if ((0 > _ldapPort) || (_ldapPort > 65535)) {
166: if (_logger.isErrorEnabled()) {
167: _logger
168: .error("LDAP port is not in valid range [0,65535].");
169: }
170:
171: throw new NumberFormatException();
172: }
173: } catch (NumberFormatException nfe) {
174: String msg = "Invalid LDAP port '" + port
175: + "' specified.";
176: if (_logger.isErrorEnabled()) {
177: _logger.error(msg);
178: }
179:
180: throw new ConfigurationException(msg);
181: }
182: }
183:
184: _bindingUser = _servletConfig
185: .getInitParameter(BLOG_LDAP_AUTHORIZATION_BINDING_USER_IP);
186: _bindingPassword = _servletConfig
187: .getInitParameter(BLOG_LDAP_AUTHORIZATION_BINDING_PASSWORD_IP);
188:
189: if (_logger.isDebugEnabled()) {
190: _logger.debug("LDAP Authorization Provider server: "
191: + _ldapServer);
192: _logger.debug("LDAP Authorization Provider port: "
193: + _ldapPort);
194: _logger.debug("LDAP Authorization Provider DN: " + _ldapDN);
195: _logger.debug("LDAP Authorization Provider UID: "
196: + _uidAttributeName);
197: _logger.debug("LDAP Authorization Provider binding user: "
198: + _bindingUser);
199: _logger
200: .debug("LDAP Authorization Provider binding password: **********");
201: _logger.debug("LDAP Authorization Provider UseSSL: "
202: + _useSSL);
203:
204: _logger.debug("Initialized LDAP authorization provider");
205: }
206: }
207:
208: /**
209: * Authorize a username and password for the given {@link Blog}
210: *
211: * @param blog {@link Blog}
212: * @param authorizationContext {@link Map} to be used to provide other information for authorization. This will
213: * change depending on the authorization provider. This parameter is not used in this implementation.
214: * @param username Username. In this implementation, this value must match that of the blog user's ID.
215: * @param password Password
216: * @throws AuthorizationException If there is an error authorizing the username and password
217: */
218: public void authorize(Blog blog, Map authorizationContext,
219: String username, String password)
220: throws AuthorizationException {
221: String dn = getDN(username);
222:
223: if (BlojsomUtils.checkNullOrBlank(_ldapServer)
224: || BlojsomUtils.checkNullOrBlank(dn)) {
225: String msg = "Authorization failed for blog: "
226: + blog.getBlogId() + " for username: " + username
227: + "; " + "LDAP not properly configured";
228: if (_logger.isErrorEnabled()) {
229: _logger.error(msg);
230: }
231:
232: throw new AuthorizationException(msg);
233: }
234:
235: try {
236: LDAPConnection ldapConnection;
237:
238: if (_useSSL) {
239: JSSESocketFactory ldapSocketFactory = new JSSESocketFactory();
240: ldapConnection = new LDAPConnection(ldapSocketFactory);
241: } else {
242: ldapConnection = new LDAPConnection();
243: }
244:
245: // Connect to the directory server
246: ldapConnection.connect(_ldapServer, _ldapPort);
247:
248: if (blog.getUseEncryptedPasswords().booleanValue()) {
249: password = BlojsomUtils.digestString(password, blog
250: .getDigestAlgorithm());
251: }
252:
253: // Use simple authentication. The first argument
254: // specifies the version of the LDAP protocol used.
255: ldapConnection.authenticate(3, dn, password);
256:
257: ldapConnection.disconnect();
258: if (_logger.isDebugEnabled()) {
259: _logger.debug("Successfully authenticated user '"
260: + username + "' via LDAP.");
261: }
262: } catch (LDAPException e) {
263: String reason;
264: switch (e.getLDAPResultCode()) {
265: // The DN does not correspond to any existing entry
266: case LDAPException.NO_SUCH_OBJECT:
267: reason = "The specified user does not exist: " + dn;
268: break;
269: // The password is incorrect
270: case LDAPException.INVALID_CREDENTIALS:
271: reason = "Invalid password";
272: break;
273: // Some other error occurred
274: default:
275: reason = "Failed to authenticate as " + dn + ", " + e;
276: break;
277: }
278:
279: String msg = "Authorization failed for blog: "
280: + blog.getBlogId() + " for username: " + username
281: + "; " + reason;
282:
283: if (_logger.isErrorEnabled()) {
284: _logger.error(msg);
285: }
286:
287: throw new AuthorizationException(msg);
288: }
289: }
290:
291: /**
292: * Get the DN for a given username
293: *
294: * @param username Username
295: * @return DN for a given username or <code>null</code> if there is an exception in lookup
296: */
297: protected String getDN(String username) {
298: try {
299: LDAPConnection ldapConnection;
300:
301: if (_useSSL) {
302: JSSESocketFactory ldapSocketFactory = new JSSESocketFactory();
303: ldapConnection = new LDAPConnection(ldapSocketFactory);
304: } else {
305: ldapConnection = new LDAPConnection();
306: }
307:
308: // Connect to the directory server
309: ldapConnection.connect(_ldapServer, _ldapPort);
310:
311: // Authenticate with the server
312: if (!BlojsomUtils.checkNullOrBlank(_bindingUser)
313: && !BlojsomUtils.checkNullOrBlank(_bindingPassword)) {
314: if (_logger.isDebugEnabled()) {
315: _logger
316: .debug("Using LDAP authentication for LDAP connection");
317: }
318:
319: ldapConnection.authenticate(3, _bindingUser,
320: _bindingPassword);
321: }
322:
323: // Search for the dn of the user given the username (uid).
324: String[] attrs = {};
325: LDAPSearchResults res = ldapConnection.search(_ldapDN,
326: LDAPv2.SCOPE_SUB, "(" + _uidAttributeName + "="
327: + username + ")", attrs, true);
328:
329: if (!res.hasMoreElements()) {
330: // No such user.
331: if (_logger.isDebugEnabled()) {
332: _logger.debug("User '" + username
333: + "' does not exist in LDAP directory.");
334: }
335:
336: ldapConnection.disconnect();
337:
338: return null;
339: }
340:
341: String dn = res.next().getDN();
342: ldapConnection.disconnect();
343: if (_logger.isDebugEnabled()) {
344: _logger.debug("Successfully got user DN '" + dn
345: + "' via LDAP.");
346: }
347:
348: return dn;
349: } catch (LDAPException e) {
350: // Some exception occurred above; the search for the dn failed.
351: return null;
352: }
353: }
354:
355: /**
356: * Get a specific attribute value for a given username
357: *
358: * @param username Username
359: * @param attribute Attribute
360: * @return attribute value for a given username or <code>null</code> if there is an exception in lookup
361: */
362: protected String getAttribute(String username, String attribute) {
363: LDAPConnection ldapConnection = null;
364: String value = null;
365:
366: try {
367: // Connect to the server.
368: if (_useSSL) {
369: JSSESocketFactory ldapSocketFactory = new JSSESocketFactory();
370: ldapConnection = new LDAPConnection(ldapSocketFactory);
371: } else {
372: ldapConnection = new LDAPConnection();
373: }
374:
375: ldapConnection.connect(_ldapServer, _ldapPort);
376:
377: // Authenticate with the server
378: if (!BlojsomUtils.checkNullOrBlank(_bindingUser)
379: && !BlojsomUtils.checkNullOrBlank(_bindingPassword)) {
380: if (_logger.isDebugEnabled()) {
381: _logger
382: .debug("Using LDAP authentication for LDAP connection");
383: }
384:
385: ldapConnection.authenticate(3, _bindingUser,
386: _bindingPassword);
387: }
388:
389: // Send the search request.
390: String attrs[] = { attribute };
391: LDAPSearchResults res = ldapConnection.search(_ldapDN,
392: LDAPConnection.SCOPE_SUB, "(" + _uidAttributeName
393: + "=" + username + ")", attrs, false);
394:
395: // Iterate through and print out the results.
396: while (res.hasMoreElements()) {
397: // Get the next directory entry.
398: LDAPEntry findEntry = null;
399:
400: try {
401: findEntry = res.next();
402: } catch (LDAPException e) {
403: if (_logger.isErrorEnabled()) {
404: _logger.error("Error: " + e.toString());
405: }
406:
407: continue;
408: }
409:
410: // Print the DN of the entry.
411: if (_logger.isDebugEnabled()) {
412: _logger.debug(findEntry.getDN());
413: }
414:
415: // Get the attributes of the entry
416: LDAPAttributeSet findAttrs = findEntry
417: .getAttributeSet();
418: Enumeration enumAttrs = findAttrs.getAttributes();
419:
420: if (_logger.isDebugEnabled()) {
421: _logger.debug("\tAttributes: ");
422: }
423:
424: // Loop on attributes
425: while (enumAttrs.hasMoreElements()) {
426: LDAPAttribute anAttr = (LDAPAttribute) enumAttrs
427: .nextElement();
428: String attrName = anAttr.getName();
429: if (_logger.isDebugEnabled()) {
430: _logger.debug("\t\t" + attrName);
431: }
432:
433: // Loop on values for this attribute
434: Enumeration enumVals = anAttr.getStringValues();
435: if (enumVals != null) {
436: while (enumVals.hasMoreElements()) {
437: String aVal = (String) enumVals
438: .nextElement();
439: value = aVal;
440:
441: if (_logger.isDebugEnabled()) {
442: _logger.debug("\t\t\t" + aVal);
443: }
444: }
445: }
446: }
447: }
448: } catch (LDAPException e) {
449: if (_logger.isErrorEnabled()) {
450: _logger.error("Error: " + e.toString());
451: }
452: }
453:
454: // Done, so disconnect
455: if ((ldapConnection != null) && ldapConnection.isConnected()) {
456: try {
457: ldapConnection.disconnect();
458: } catch (LDAPException e) {
459: if (_logger.isErrorEnabled()) {
460: _logger.error("Error: " + e.toString());
461: }
462: }
463: }
464:
465: return value;
466: }
467:
468: /**
469: * Return the LDAP server name
470: *
471: * @return LDAP server name
472: */
473: protected String getServer() {
474: return _ldapServer;
475: }
476:
477: /**
478: * Return the LDAP server port
479: *
480: * @return LDAP server port
481: */
482: protected int getPort() {
483: return _ldapPort;
484: }
485:
486: /**
487: * Return the LDAP base DN
488: *
489: * @return LDAP base DN
490: */
491: protected String getBaseDN() {
492: return _ldapDN;
493: }
494: }
|