001: /*
002: * Authenticate.java
003: *
004: * Version: $Revision: 1.3 $
005: *
006: * Date: $Date: 2006/07/05 21:39:29 $
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.app.xmlui.utils;
041:
042: import java.sql.SQLException;
043: import java.util.Map;
044:
045: import javax.servlet.http.HttpServletRequest;
046: import javax.servlet.http.HttpSession;
047:
048: import org.apache.cocoon.environment.http.HttpEnvironment;
049: import org.apache.log4j.Logger;
050: import org.dspace.authenticate.AuthenticationManager;
051: import org.dspace.authenticate.AuthenticationMethod;
052: import org.dspace.authorize.AuthorizeException;
053: import org.dspace.core.Context;
054: import org.dspace.core.LogManager;
055: import org.dspace.eperson.EPerson;
056:
057: /**
058: * Methods for authenticating the user. This is DSpace platform code, as opposed
059: * to the site-specific authentication code, that resides in implementations of
060: * the org.dspace.eperson.AuthenticationMethod interface.
061: *
062: * @author Scott Phillips
063: * @author Robert Tansley
064: */
065:
066: public class AuthenticationUtil {
067: private static final Logger log = Logger
068: .getLogger(AuthenticationUtil.class);
069:
070: /**
071: * Session attribute name for storing the return url where the user should
072: * be redirected too once successfully authenticated.
073: */
074: public static final String REQUEST_INTERRUPTED = "dspace.request.interrupted";
075: public static final String REQUEST_RESUME = "dspace.request.resume";
076:
077: /**
078: * These store a message giving a reason for why the request is being interrupted.
079: */
080: public static final String REQUEST_INTERRUPTED_HEADER = "dspace.request.interrupted.header";
081: public static final String REQUEST_INTERRUPTED_MESSAGE = "dspace.request.interrupted.message";
082: public static final String REQUEST_INTERRUPTED_CHARACTERS = "dspace.request.interrupted.characters";
083:
084: /**
085: * Session attribute names to store the current user & id.
086: */
087: private static final String CURRENT_USER = "dspace.current.user";
088:
089: private static final String CURRENT_USER_ID = "dspace.current.user.id";
090:
091: private static final String CURRENT_USER_ADDRESS = "dspace.current.user.address";
092:
093: /**
094: * Authenticate the current DSpace content based upon given authentication
095: * credentials. The AuthenticationManager will consult the configured
096: * authentication stack to determine the best method.
097: *
098: * @param objectModel
099: * Cocoon's object model.
100: * @param email
101: * The email credentials provided by the user.
102: * @param password
103: * The password credentials provided by the user.
104: * @param realm
105: * The realm credentials proveded by the user.
106: * @return Return a current context with either the eperson attached if the
107: * authentication was successfull or or no eperson attached if the
108: * attempt failed.
109: */
110: public static Context Authenticate(Map objectModel, String email,
111: String password, String realm) throws SQLException {
112: // Get the real HttpRequest
113: HttpServletRequest request = (HttpServletRequest) objectModel
114: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
115: Context context = ContextUtil.obtainContext(objectModel);
116:
117: int implicitStatus = AuthenticationManager
118: .authenticateImplicit(context, null, null, null,
119: request);
120:
121: if (implicitStatus == AuthenticationMethod.SUCCESS) {
122: log.info(LogManager.getHeader(context, "login",
123: "type=implicit"));
124: AuthenticationUtil.loggedIn(context, request, context
125: .getCurrentUser());
126: } else {
127: // If implicit authentication failed, fall over to explicit.
128:
129: int explicitStatus = AuthenticationManager.authenticate(
130: context, email, password, realm, request);
131:
132: if (explicitStatus == AuthenticationMethod.SUCCESS) {
133: // Logged in OK.
134: log.info(LogManager.getHeader(context, "login",
135: "type=explicit"));
136: AuthenticationUtil.loggedIn(context, request, context
137: .getCurrentUser());
138: } else {
139: log.info(LogManager.getHeader(context, "failed_login",
140: "email=" + email + ", realm=" + realm
141: + ", result=" + explicitStatus));
142: }
143: }
144:
145: return context;
146: }
147:
148: /**
149: * Preform implicite authentication. The authenticationManager will consult
150: * the authentication stack for any methods that can implicitly authenticate
151: * this session. If the attempt was successfull then the returned context
152: * will have an eperson attached other wise the context will not have an
153: * eperson attached.
154: *
155: * @param objectModel
156: * Cocoon's object model.
157: * @return This requests DSpace context.
158: */
159: public static Context AuthenticateImplicit(Map objectModel)
160: throws SQLException {
161: // Get the real HttpRequest
162: final HttpServletRequest request = (HttpServletRequest) objectModel
163: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
164: Context context = ContextUtil.obtainContext(objectModel);
165:
166: int implicitStatus = AuthenticationManager
167: .authenticateImplicit(context, null, null, null,
168: request);
169:
170: if (implicitStatus == AuthenticationMethod.SUCCESS) {
171: log.info(LogManager.getHeader(context, "login",
172: "type=implicit"));
173: AuthenticationUtil.loggedIn(context, request, context
174: .getCurrentUser());
175: }
176:
177: return context;
178: }
179:
180: /**
181: * Store information about the current user in the request and context
182: *
183: * @param context
184: * DSpace context
185: * @param request
186: * HTTP request
187: * @param eperson
188: * the eperson logged in
189: */
190: public static void loggedIn(Context context,
191: HttpServletRequest request, EPerson eperson)
192: throws SQLException {
193: if (eperson == null)
194: return;
195:
196: HttpSession session = request.getSession();
197:
198: context.setCurrentUser(eperson);
199:
200: // Set any special groups - invoke the authentication mgr.
201: int[] groupIDs = AuthenticationManager.getSpecialGroups(
202: context, request);
203:
204: for (int groupID : groupIDs)
205: context.setSpecialGroup(groupID);
206:
207: // We store the current user in the request as an EPerson object...
208: request.setAttribute(CURRENT_USER, eperson);
209:
210: // and in the session as an ID
211: session.setAttribute(CURRENT_USER_ID, eperson.getID());
212:
213: // and the remote IP address to compare against later requests
214: // so we can detect session hijacking.
215: session.setAttribute(CURRENT_USER_ADDRESS, request
216: .getRemoteAddr());
217: }
218:
219: public static void loggedIn(Map objectModel, EPerson eperson)
220: throws SQLException {
221: final HttpServletRequest request = (HttpServletRequest) objectModel
222: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
223: Context context = ContextUtil.obtainContext(objectModel);
224:
225: loggedIn(context, request, eperson);
226: }
227:
228: /**
229: * Resume any previous login.
230: *
231: * @param context
232: * DSpace context
233: * @param request
234: * HTTP Request
235: */
236: public static void resumeLogin(Context context,
237: HttpServletRequest request) throws SQLException {
238: HttpSession session = request.getSession(false);
239:
240: if (session != null) {
241: Integer id = (Integer) session
242: .getAttribute(CURRENT_USER_ID);
243:
244: if (id != null) {
245: String address = (String) session
246: .getAttribute(CURRENT_USER_ADDRESS);
247: if (address != null
248: && address.equals(request.getRemoteAddr())) {
249: EPerson eperson = EPerson.find(context, id);
250: loggedIn(context, request, eperson);
251: } else {
252: // Possible hack attempt.
253: }
254: } // if id
255: } // if session
256: }
257:
258: /**
259: * Log the user out.
260: *
261: * @param context
262: * DSpace context
263: * @param request
264: * HTTP request
265: */
266: public static void loggedOut(Context context,
267: HttpServletRequest request) {
268: HttpSession session = request.getSession();
269:
270: context.setCurrentUser(null);
271: request.removeAttribute(CURRENT_USER);
272: session.removeAttribute(CURRENT_USER_ID);
273: session.removeAttribute(CURRENT_USER_ADDRESS);
274: }
275:
276: /**
277: * Determine if the email can register them selfs or need to be
278: * created by a site administrator first.
279: *
280: * @param objectModel
281: * The Cocoon object model
282: * @param email
283: * The email of the person to be registered.
284: * @return true if the email can register, otherwise false.
285: */
286: public static boolean canSelfRegister(Map objectModel, String email)
287: throws SQLException {
288: final HttpServletRequest request = (HttpServletRequest) objectModel
289: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
290: Context context = ContextUtil.obtainContext(objectModel);
291:
292: return AuthenticationManager.canSelfRegister(context, request,
293: email);
294: }
295:
296: /**
297: * Determine if the EPerson (to be created or allready created) has the
298: * ability to set their own password.
299: *
300: * @param objectModel
301: * The Cocoon object model
302: * @param email
303: * The email address of the EPerson.
304: * @return
305: */
306: public static boolean allowSetPassword(Map objectModel, String email)
307: throws SQLException {
308: final HttpServletRequest request = (HttpServletRequest) objectModel
309: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
310: Context context = ContextUtil.obtainContext(objectModel);
311:
312: return AuthenticationManager.allowSetPassword(context, request,
313: email);
314: }
315:
316: /**
317: * Construct a new, mostly blank, eperson for the given email address. This should
318: * only be called once the email address has been verified.
319: *
320: * @param objectModel
321: * The Cocoon object model.
322: * @param email
323: * The email address of the new eperson.
324: * @return A newly created EPerson object.
325: */
326: public static EPerson createNewEperson(Map objectModel, String email)
327: throws SQLException, AuthorizeException {
328: final HttpServletRequest request = (HttpServletRequest) objectModel
329: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
330: Context context = ContextUtil.obtainContext(objectModel);
331:
332: // Need to create new eperson
333: // FIXME: TEMPORARILY need to turn off authentication, as usually
334: // only site admins can create e-people
335: context.setIgnoreAuthorization(true);
336: EPerson eperson = EPerson.create(context);
337: eperson.setEmail(email);
338: eperson.setCanLogIn(true);
339: eperson.setSelfRegistered(true);
340: eperson.update();
341: context.setIgnoreAuthorization(false);
342:
343: // Give site auth a chance to set/override appropriate fields
344: AuthenticationManager.initEPerson(context, request, eperson);
345:
346: return eperson;
347: }
348:
349: /**
350: * Is there a currently interuppted request?
351: *
352: * @param objectModel The Cocoon object Model
353: */
354: public static boolean isInterupptedRequest(Map objectModel) {
355: final HttpServletRequest request = (HttpServletRequest) objectModel
356: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
357:
358: HttpSession session = request.getSession();
359:
360: Object interruptedObject = session
361: .getAttribute(REQUEST_INTERRUPTED);
362:
363: if (interruptedObject instanceof RequestInfo) {
364: // There is currently either an interrupted or yet-to-be resumed request.
365: return true;
366: }
367:
368: // There are not interupted requests.
369: return false;
370: }
371:
372: /**
373: * Interrupt the current request and store if for later resumption. This request will
374: * send an http redirect telling the client to authenticate first. Once that has been finished
375: * then the request can be resumed.
376: *
377: * @param objectModel The Cocoon object Model
378: * @param header A message header (i18n tag)
379: * @param message A message for why the request was interrupted (i18n tag)
380: * @param characters An untranslated messsage, perhaps an error message?
381: */
382: public static void interruptRequest(Map objectModel, String header,
383: String message, String characters) {
384: final HttpServletRequest request = (HttpServletRequest) objectModel
385: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
386:
387: HttpSession session = request.getSession();
388:
389: // Store this interrupted request untill after the user successfully authenticates.
390: RequestInfo interruptedRequest = new RequestInfo(request);
391:
392: // Set the request as interrupted
393: session.setAttribute(REQUEST_INTERRUPTED, interruptedRequest);
394: session.setAttribute(REQUEST_RESUME, null); // just to be clear.
395:
396: // Set the interrupt message
397: session.setAttribute(REQUEST_INTERRUPTED_HEADER, header);
398: session.setAttribute(REQUEST_INTERRUPTED_MESSAGE, message);
399: session
400: .setAttribute(REQUEST_INTERRUPTED_CHARACTERS,
401: characters);
402:
403: }
404:
405: /**
406: * Set the interrupted request to a resumable state. The
407: * next request that the server recieves (for this session) that
408: * has the same servletPath will be replaced with the previously
409: * inturrupted request.
410: *
411: * @param objectModel The Cocoon object Model
412: * @return
413: */
414: public static String resumeInterruptedRequest(Map objectModel) {
415: final HttpServletRequest request = (HttpServletRequest) objectModel
416: .get(HttpEnvironment.HTTP_REQUEST_OBJECT);
417:
418: HttpSession session = request.getSession();
419:
420: // Clear the interrupt message
421: session.setAttribute(
422: AuthenticationUtil.REQUEST_INTERRUPTED_HEADER, null);
423: session.setAttribute(
424: AuthenticationUtil.REQUEST_INTERRUPTED_MESSAGE, null);
425: session
426: .setAttribute(
427: AuthenticationUtil.REQUEST_INTERRUPTED_CHARACTERS,
428: null);
429:
430: // Set the request as interrupted
431: Object interruptedObject = session
432: .getAttribute(REQUEST_INTERRUPTED);
433: if (interruptedObject instanceof RequestInfo) {
434: RequestInfo interruptedRequest = (RequestInfo) interruptedObject;
435:
436: session.setAttribute(REQUEST_INTERRUPTED, null);
437: session.setAttribute(REQUEST_RESUME, interruptedRequest);
438:
439: // Return the path for which this request belongs too. Only urls
440: // for this path may be resumed.
441: return interruptedRequest.getServletPath();
442: }
443:
444: // No request was interrupted.
445: return null;
446: }
447:
448: /**
449: * Check to see if this request should be resumed.
450: *
451: * @param realHttpRequest The current real request
452: * @return Either the current real request or a stored request that was previously interrupted.
453: */
454: public static HttpServletRequest resumeRequest(
455: HttpServletRequest realHttpRequest) {
456: // First check to see if there is a resumed request.
457: HttpSession session = realHttpRequest.getSession();
458: //session.setMaxInactiveInterval(60);
459: Object object = session.getAttribute(REQUEST_RESUME);
460:
461: // Next check to make sure it's the right type of object,
462: // there should be no condition where it is not - but always
463: // safe to check.
464: if (object instanceof RequestInfo) {
465: RequestInfo interruptedRequest = (RequestInfo) object;
466:
467: // Next, check to make sure this real request if for the same url
468: // path, if so then resume the previous request.
469: String interruptedServletPath = interruptedRequest
470: .getServletPath();
471: String realServletPath = realHttpRequest.getServletPath();
472:
473: if (realServletPath != null
474: && realServletPath.equals(interruptedServletPath)) {
475: // Clear the resumed request and send the request back to be resumed.
476: session.setAttribute(REQUEST_INTERRUPTED, null);
477: session.setAttribute(REQUEST_RESUME, null);
478:
479: return interruptedRequest.wrapRequest(realHttpRequest);
480: }
481: }
482: // Otherwise return the real request.
483: return realHttpRequest;
484: }
485:
486: }
|