001: /*
002: * AuthenticationManager.java
003: *
004: * Version: $Revision: 2168 $
005: *
006: * Date: $Date: 2007-08-27 17:40:09 -0500 (Mon, 27 Aug 2007) $
007: *
008: * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */
040: package org.dspace.authenticate;
041:
042: import java.io.IOException;
043:
044: import javax.servlet.ServletException;
045: import javax.servlet.http.HttpServletRequest;
046: import javax.servlet.http.HttpServletResponse;
047: import javax.servlet.http.HttpSession;
048: import java.sql.SQLException;
049: import java.util.ArrayList;
050: import java.util.Arrays;
051: import java.util.List;
052: import java.util.Iterator;
053: import java.util.StringTokenizer;
054:
055: import org.apache.log4j.Logger;
056: import org.dspace.core.ConfigurationManager;
057: import org.dspace.core.Context;
058: import org.dspace.core.PluginManager;
059: import org.dspace.core.LogManager;
060: import org.dspace.eperson.EPerson;
061:
062: /**
063: * Access point for the stackable authentication methods.
064: * <p>
065: * This class initializes the "stack" from the DSpace configuration,
066: * and then invokes methods in the appropriate order on behalf of clients.
067: * <p>
068: * See the AuthenticationMethod interface for details about what each
069: * function does.
070: * <p>
071: * <b>Configuration</b><br>
072: * The stack of authentication methods is defined by one property in the DSpace configuration:
073: * <pre>
074: * plugin.sequence.org.dspace.eperson.AuthenticationMethod = <em>a list of method class names</em>
075: * <em>e.g.</em>
076: * plugin.sequence.org.dspace.eperson.AuthenticationMethod = \
077: * org.dspace.eperson.X509Authentication, \
078: * org.dspace.eperson.PasswordAuthentication
079: * </pre>
080: * <p>
081: * The "stack" is always traversed in order, with the methods
082: * specified first (in the configuration) thus getting highest priority.
083: *
084: * @see AuthenticationMethod
085: *
086: * @author Larry Stone
087: * @version $Revision: 2168 $
088: */
089: public class AuthenticationManager {
090: /** log4j category */
091: private static Logger log = Logger
092: .getLogger(AuthenticationManager.class);
093:
094: /** List of authentication methods, highest precedence first. */
095: private static AuthenticationMethod methodStack[] = (AuthenticationMethod[]) PluginManager
096: .getPluginSequence(AuthenticationMethod.class);
097:
098: /**
099: * Test credentials for authenticity.
100: * Apply the given credentials to each authenticate() method in
101: * the stack. Returns upon the first <code>SUCCESS</code>, or otherwise
102: * returns the most favorable outcome from one of the methods.
103: *
104: * @param context
105: * DSpace context, will be modified (ePerson set) upon success.
106: *
107: * @param username
108: * Username (or email address) when method is explicit. Use null for
109: * implicit method.
110: *
111: * @param password
112: * Password for explicit auth, or null for implicit method.
113: *
114: * @param realm
115: * Realm is an extra parameter used by some authentication methods, leave null if
116: * not applicable.
117: *
118: * @param request
119: * The HTTP request that started this operation, or null if not applicable.
120: *
121: * @return One of:
122: * SUCCESS, BAD_CREDENTIALS, CERT_REQUIRED, NO_SUCH_USER, BAD_ARGS
123: * <p>Meaning:
124: * <br>SUCCESS - authenticated OK.
125: * <br>BAD_CREDENTIALS - user exists, but credenitals (e.g. passwd) don't match
126: * <br>CERT_REQUIRED - not allowed to login this way without X.509 cert.
127: * <br>NO_SUCH_USER - user not found using this method.
128: * <br>BAD_ARGS - user/pw not appropriate for this method
129: */
130: public static int authenticate(Context context, String username,
131: String password, String realm, HttpServletRequest request) {
132: return authenticateInternal(context, username, password, realm,
133: request, false);
134: }
135:
136: /**
137: * Test credentials for authenticity, using only Implicit methods.
138: * Just like <code>authenticate()</code>, except it only invokes the
139: * <em>implicit</em> authentication methods the stack.
140: *
141: * @param context
142: * DSpace context, will be modified (ePerson set) upon success.
143: *
144: * @param username
145: * Username (or email address) when method is explicit. Use null for
146: * implicit method.
147: *
148: * @param password
149: * Password for explicit auth, or null for implicit method.
150: *
151: * @param realm
152: * Realm is an extra parameter used by some authentication methods, leave null if
153: * not applicable.
154: *
155: * @param request
156: * The HTTP request that started this operation, or null if not applicable.
157: *
158: * @return One of:
159: * SUCCESS, BAD_CREDENTIALS, CERT_REQUIRED, NO_SUCH_USER, BAD_ARGS
160: * <p>Meaning:
161: * <br>SUCCESS - authenticated OK.
162: * <br>BAD_CREDENTIALS - user exists, but credenitals (e.g. passwd) don't match
163: * <br>CERT_REQUIRED - not allowed to login this way without X.509 cert.
164: * <br>NO_SUCH_USER - user not found using this method.
165: * <br>BAD_ARGS - user/pw not appropriate for this method
166: */
167: public static int authenticateImplicit(Context context,
168: String username, String password, String realm,
169: HttpServletRequest request) {
170: return authenticateInternal(context, username, password, realm,
171: request, true);
172: }
173:
174: private static int authenticateInternal(Context context,
175: String username, String password, String realm,
176: HttpServletRequest request, boolean implicitOnly) {
177: // better is lowest, so start with the highest.
178: int bestRet = AuthenticationMethod.BAD_ARGS;
179:
180: // return on first success, otherwise "best" outcome.
181: for (int i = 0; i < methodStack.length; ++i) {
182: if (!implicitOnly || methodStack[i].isImplicit()) {
183: int ret = 0;
184: try {
185: ret = methodStack[i].authenticate(context,
186: username, password, realm, request);
187: } catch (SQLException e) {
188: ret = AuthenticationMethod.NO_SUCH_USER;
189: }
190: if (ret == AuthenticationMethod.SUCCESS)
191: return ret;
192: if (ret < bestRet)
193: bestRet = ret;
194: }
195: }
196: return bestRet;
197: }
198:
199: /**
200: * Predicate, can a new EPerson be created.
201: * Invokes <code>canSelfRegister()</code> of every authentication
202: * method in the stack, and returns true if any of them is true.
203: *
204: * @param context
205: * DSpace context
206: * @param request
207: * HTTP request, in case it's needed. Can be null.
208: * @param username
209: * Username, if available. Can be null.
210: * @return true if new ePerson should be created.
211: */
212: public static boolean canSelfRegister(Context context,
213: HttpServletRequest request, String username)
214: throws SQLException {
215: for (int i = 0; i < methodStack.length; ++i)
216: if (methodStack[i].canSelfRegister(context, request,
217: username))
218: return true;
219: return false;
220: }
221:
222: /**
223: * Predicate, can user set EPerson password.
224: * Returns true if the <code>allowSetPassword()</code> method of any
225: * member of the stack returns true.
226: *
227: * @param context
228: * DSpace context
229: * @param request
230: * HTTP request, in case it's needed. Can be null.
231: * @param username
232: * Username, if available. Can be null.
233: * @return true if this method allows user to change ePerson password.
234: */
235: public static boolean allowSetPassword(Context context,
236: HttpServletRequest request, String username)
237: throws SQLException {
238: for (int i = 0; i < methodStack.length; ++i)
239: if (methodStack[i].allowSetPassword(context, request,
240: username))
241: return true;
242: return false;
243: }
244:
245: /**
246: * Initialize a new e-person record for a self-registered new user.
247: * Give every authentication method in the stack a chance to
248: * initialize the new ePerson by calling its <code>initEperson()</code>
249: *
250: * @param context
251: * DSpace context
252: * @param request
253: * HTTP request, in case it's needed. Can be null.
254: * @param eperson
255: * newly created EPerson record - email + information from the
256: * registration form will have been filled out.
257: */
258: public static void initEPerson(Context context,
259: HttpServletRequest request, EPerson eperson)
260: throws SQLException {
261: for (int i = 0; i < methodStack.length; ++i)
262: methodStack[i].initEPerson(context, request, eperson);
263: }
264:
265: /**
266: * Get list of extra groups that user implicitly belongs to.
267: * Returns accumulation of groups of all the <code>getSpecialGroups()</code>
268: * methods in the stack.
269: *
270: * @param context
271: * A valid DSpace context.
272: *
273: * @param request
274: * The request that started this operation, or null if not applicable.
275: *
276: * @return Returns IDs of any groups the user authenticated by this
277: * request is in implicitly -- checks for e.g. network-address dependent
278: * groups.
279: */
280: public static int[] getSpecialGroups(Context context,
281: HttpServletRequest request) throws SQLException {
282: ArrayList gll = new ArrayList();
283: int totalLen = 0;
284:
285: for (int i = 0; i < methodStack.length; ++i) {
286: int gl[] = methodStack[i]
287: .getSpecialGroups(context, request);
288: if (gl.length > 0) {
289: gll.add(gl);
290: totalLen += gl.length;
291: }
292: }
293:
294: // Maybe this is over-optimized but it's called on every
295: // request, and most sites will only have 0 or 1 auth methods
296: // actually returning groups, so it pays..
297: if (totalLen == 0)
298: return new int[0];
299: else if (gll.size() == 1)
300: return (int[]) gll.get(0);
301: else {
302: // Have to do it this painful way since list.toArray() doesn't
303: // work on int[]. stupid Java ints aren't first-class objects.
304: int result[] = new int[totalLen];
305: int k = 0;
306: for (int i = 0; i < gll.size(); ++i) {
307: int gl[] = (int[]) gll.get(i);
308: for (int j = 0; j < gl.length; ++j)
309: result[k++] = gl[j];
310: }
311: return result;
312: }
313: }
314:
315: /**
316: * Get stack of authentication methods.
317: * Return an <code>Iterator</code> that steps through each configured
318: * authentication method, in order of precedence.
319: *
320: * @return Iterator object.
321: */
322: public static Iterator authenticationMethodIterator() {
323: return Arrays.asList(methodStack).iterator();
324: }
325: }
|