/* * Authenticate.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 18:02:24 +0100 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.xmlui.utils; import java.sql.SQLException; import java.util.Map; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.Request; import org.apache.cocoon.environment.http.HttpEnvironment; import org.apache.log4j.Logger; import org.dspace.app.statistics.AbstractUsageEvent; import org.dspace.app.xmlui.aspect.administrative.SystemwideAlerts; import org.dspace.authenticate.AuthenticationManager; import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeManager; import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; /** * Methods for authenticating the user. This is DSpace platform code, as opposed * to the site-specific authentication code, that resides in implementations of * the org.dspace.eperson.AuthenticationMethod interface. * * @author Scott Phillips * @author Robert Tansley */ public class AuthenticationUtil { private static final Logger log = Logger.getLogger(AuthenticationUtil.class); /** * Session attribute name for storing the return url where the user should * be redirected too once successfully authenticated. */ public static final String REQUEST_INTERRUPTED = "dspace.request.interrupted"; public static final String REQUEST_RESUME = "dspace.request.resume"; /** * These store a message giving a reason for why the request is being interrupted. */ public static final String REQUEST_INTERRUPTED_HEADER = "dspace.request.interrupted.header"; public static final String REQUEST_INTERRUPTED_MESSAGE = "dspace.request.interrupted.message"; public static final String REQUEST_INTERRUPTED_CHARACTERS = "dspace.request.interrupted.characters"; // GWaller 4/10/10 Added constants for storing a URL which should be requested after authentication in the user's cookies public static final String COOKIE_REDIRECT_NAME_CONFIG_KEY = "webui.authutil.redirect.cookie.name"; public static final String COOKIE_REDIRECT_NAME_DEFAULT_VALUE = "cookieRedirectPath"; public static final String COOKIE_REDIRECT_MAX_AGE_CONFIG_KEY = "webui.authutil.redirect.cookie.maxage.seconds"; /** * The IP address this user first logged in from, do not allow this session for * other IP addresses. */ private static final String CURRENT_IP_ADDRESS = "dspace.user.ip"; /** * The effective user id, typically this will never change. However if an administrator * has assumed login as this user then they will differ. */ private static final String EFFECTIVE_USER_ID = "dspace.user.effective"; private static final String AUTHENTICATED_USER_ID = "dspace.user.authenticated"; /** * Authenticate the current DSpace content based upon given authentication * credentials. The AuthenticationManager will consult the configured * authentication stack to determine the best method. * * @param objectModel * Cocoon's object model. * @param email * The email credentials provided by the user. * @param password * The password credentials provided by the user. * @param realm * The realm credentials proveded by the user. * @return Return a current context with either the eperson attached if the * authentication was successfull or or no eperson attached if the * attempt failed. */ public static Context Authenticate(Map objectModel, String email, String password, String realm) throws SQLException { // Get the real HttpRequest HttpServletRequest request = (HttpServletRequest) objectModel .get(HttpEnvironment.HTTP_REQUEST_OBJECT); Context context = ContextUtil.obtainContext(objectModel); int implicitStatus = AuthenticationManager.authenticateImplicit( context, null, null, null, request); if (implicitStatus == AuthenticationMethod.SUCCESS) { log.info(LogManager.getHeader(context, "login", "type=implicit")); AuthenticationUtil.logIn(context, request, context.getCurrentUser()); } else { // If implicit authentication failed, fall over to explicit. int explicitStatus = AuthenticationManager.authenticate(context, email, password, realm, request); if (explicitStatus == AuthenticationMethod.SUCCESS) { // Logged in OK. log.info(LogManager .getHeader(context, "login", "type=explicit")); AuthenticationUtil.logIn(context, request, context .getCurrentUser()); // Added by CG to record a login in the stats db new UsageEvent().fire((Request) ObjectModelHelper.getRequest(objectModel), context, AbstractUsageEvent.LOGIN, 0, 0); } else { log.info(LogManager.getHeader(context, "failed_login", "email=" + email + ", realm=" + realm + ", result=" + explicitStatus)); } } return context; } /** * Preform implicite authentication. The authenticationManager will consult * the authentication stack for any methods that can implicitly authenticate * this session. If the attempt was successfull then the returned context * will have an eperson attached other wise the context will not have an * eperson attached. * * @param objectModel * Cocoon's object model. * @return This requests DSpace context. */ public static Context AuthenticateImplicit(Map objectModel) throws SQLException { // Get the real HttpRequest final HttpServletRequest request = (HttpServletRequest) objectModel .get(HttpEnvironment.HTTP_REQUEST_OBJECT); Context context = ContextUtil.obtainContext(objectModel); int implicitStatus = AuthenticationManager.authenticateImplicit( context, null, null, null, request); if (implicitStatus == AuthenticationMethod.SUCCESS) { log.info(LogManager.getHeader(context, "login", "type=implicit")); AuthenticationUtil.logIn(context, request, context.getCurrentUser()); } return context; } /** * Log the given user in as a real authenticated user. This should only be used after * a user has presented their credintals and they have been validated. * * @param context * DSpace context * @param request * HTTP request * @param eperson * the eperson logged in */ private static void logIn(Context context, HttpServletRequest request, EPerson eperson) throws SQLException { if (eperson == null) return; HttpSession session = request.getSession(); context.setCurrentUser(eperson); // Check to see if systemwide alerts is restricting sessions if (!AuthorizeManager.isAdmin(context) && !SystemwideAlerts.canUserStartSession()) { // Do not allow this user to login because sessions are being restricted by a systemwide alert. context.setCurrentUser(null); return; } // Set any special groups - invoke the authentication mgr. int[] groupIDs = AuthenticationManager.getSpecialGroups(context, request); for (int groupID : groupIDs) context.setSpecialGroup(groupID); // and the remote IP address to compare against later requests // so we can detect session hijacking. session.setAttribute(CURRENT_IP_ADDRESS, request.getRemoteAddr()); // Set both the effective and authenticated user to the same. session.setAttribute(EFFECTIVE_USER_ID, eperson.getID()); session.setAttribute(AUTHENTICATED_USER_ID,eperson.getID()); } /** * Log the given user in as a real authenticated user. This should only be used after * a user has presented their credintals and they have been validated. This method * signature is provided to be easier to call from flow scripts. * * @param objectModel * The cocoon object model. * @param eperson * the eperson logged in * */ public static void logIn(Map objectModel, EPerson eperson) throws SQLException { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); Context context = ContextUtil.obtainContext(objectModel); logIn(context,request,eperson); } /** * Check to see if there are any session attributes indicating a currently authenticated * user. If there is then log this user in. * * @param context * DSpace context * @param request * HTTP Request */ public static void resumeLogin(Context context, HttpServletRequest request) throws SQLException { HttpSession session = request.getSession(false); if (session != null) { Integer id = (Integer) session.getAttribute(EFFECTIVE_USER_ID); Integer realid = (Integer) session.getAttribute(AUTHENTICATED_USER_ID); if (id != null) { String address = (String)session.getAttribute(CURRENT_IP_ADDRESS); // XXX Disabled the check that sessions must match IP addresses // if (address != null && address.equals(request.getRemoteAddr())) if (address != null) { EPerson eperson = EPerson.find(context, id); context.setCurrentUser(eperson); // Check to see if systemwide alerts is restricting sessions if (!AuthorizeManager.isAdmin(context) && !SystemwideAlerts.canUserMaintainSession()) { // Normal users can not maintain their sessions, check to see if this is really an // administrator loging in as someone else. EPerson realEPerson = EPerson.find(context, realid); Group administrators = Group.find(context,1); if (!administrators.isMember(realEPerson)) { // Log this user out because sessions are being restricted by a systemwide alert. context.setCurrentUser(null); return; } } // Set any special groups - invoke the authentication mgr. int[] groupIDs = AuthenticationManager.getSpecialGroups(context, request); for (int groupID : groupIDs) context.setSpecialGroup(groupID); } else { // Possible hack attempt. } } // if id } // if session } /** * Assume the login as another user. Only site administrators may preform the action. * * @param context * The current DSpace context loged in as a site administrator * @param request * The reall HTTP request. * @param loginAs * Whom to login as. */ public static void loginAs(Context context, HttpServletRequest request, EPerson loginAs ) throws SQLException, AuthorizeException { // Only allow loginAs if the administrator has allowed it. if (!ConfigurationManager.getBooleanProperty("xmlui.user.assumelogin", false)) return; // Only super administrators can login as someone else. if (!AuthorizeManager.isAdmin(context)) throw new AuthorizeException("Only site administrators may assume login as another user."); // Just to be double be sure, make sure the administrator // is the one who actualy authenticated themself. HttpSession session = request.getSession(false); Integer authenticatedID = (Integer) session.getAttribute(AUTHENTICATED_USER_ID); if (context.getCurrentUser().getID() != authenticatedID) throw new AuthorizeException("Only authenticated users whom are administrators may assume the login as another user."); // You may not assume the login of another super administrator if (loginAs == null) return; Group administrators = Group.find(context,1); if (administrators.isMember(loginAs)) throw new AuthorizeException("You may not assume the login as another super administrator."); // Success, allow the user to login as another user. context.setCurrentUser(loginAs); // Set any special groups - invoke the authentication mgr. int[] groupIDs = AuthenticationManager.getSpecialGroups(context,request); for (int groupID : groupIDs) context.setSpecialGroup(groupID); // Set both the effective and authenticated user to the same. session.setAttribute(EFFECTIVE_USER_ID, loginAs.getID()); } /** * Log the user out. * * @param context * DSpace context * @param request * HTTP request */ public static void logOut(Context context, HttpServletRequest request) throws SQLException { HttpSession session = request.getSession(); if (session.getAttribute(EFFECTIVE_USER_ID) != null && session.getAttribute(AUTHENTICATED_USER_ID) != null) { Integer effectiveID = (Integer) session.getAttribute(EFFECTIVE_USER_ID); Integer authenticatedID = (Integer) session.getAttribute(AUTHENTICATED_USER_ID); if (effectiveID.intValue() != authenticatedID.intValue()) { // The user has login in as another user, instead of logging them out, // revert back to their previous login name. EPerson authenticatedUser = EPerson.find(context, authenticatedID); context.setCurrentUser(authenticatedUser); session.setAttribute(EFFECTIVE_USER_ID, authenticatedID); return; } } // Otherwise, just log the person out as normal. context.setCurrentUser(null); session.removeAttribute(EFFECTIVE_USER_ID); session.removeAttribute(AUTHENTICATED_USER_ID); session.removeAttribute(CURRENT_IP_ADDRESS); } /** * Determine if the email can register them selfs or need to be * created by a site administrator first. * * @param objectModel * The Cocoon object model * @param email * The email of the person to be registered. * @return true if the email can register, otherwise false. */ public static boolean canSelfRegister(Map objectModel, String email) throws SQLException { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); Context context = ContextUtil.obtainContext(objectModel); if (SystemwideAlerts.canUserStartSession()) return AuthenticationManager.canSelfRegister(context,request,email); else // System wide alerts is preventing new sessions. return false; } /** * Determine if the EPerson (to be created or allready created) has the * ability to set their own password. * * @param objectModel * The Cocoon object model * @param email * The email address of the EPerson. * @return */ public static boolean allowSetPassword(Map objectModel, String email) throws SQLException { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); Context context = ContextUtil.obtainContext(objectModel); return AuthenticationManager.allowSetPassword(context, request, email); } /** * Construct a new, mostly blank, eperson for the given email address. This should * only be called once the email address has been verified. * * @param objectModel * The Cocoon object model. * @param email * The email address of the new eperson. * @return A newly created EPerson object. */ public static EPerson createNewEperson(Map objectModel, String email) throws SQLException, AuthorizeException { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); Context context = ContextUtil.obtainContext(objectModel); // Need to create new eperson // FIXME: TEMPORARILY need to turn off authentication, as usually // only site admins can create e-people context.setIgnoreAuthorization(true); EPerson eperson = EPerson.create(context); eperson.setEmail(email); eperson.setCanLogIn(true); eperson.setSelfRegistered(true); eperson.update(); context.setIgnoreAuthorization(false); // Give site auth a chance to set/override appropriate fields AuthenticationManager.initEPerson(context, request, eperson); return eperson; } // START GWaller 6/10/10 IssueID #303 Support for multiple licence options private static String getRedirectCookieName() { String cookieName = ConfigurationManager.getProperty(COOKIE_REDIRECT_NAME_CONFIG_KEY); if (cookieName == null) { cookieName = COOKIE_REDIRECT_NAME_DEFAULT_VALUE; } return cookieName; } private static Cookie getRedirectCookie(HttpServletRequest request){ Cookie redirectCookie = null; // GWaller 4/10/10 Check to see if the client has a cookie indicating a redirect URL - this could happen if the user authentication scheme was ediauth and the // user requested a resource which bounced them to authenticate. Remember, if using ediauth, when the user actually has logged in and returns to dspace // they hijack the ediauth session - they don't use their original session which was created when they weren't logged in. Storing information like the redirect // url in the anonymous session would be pointless (e.g. via @see org.dspace.app.xmlui.utils.AuthenticationUtil#interruptRequest) String redirectCookieName = getRedirectCookieName(); Cookie[] cookies = request.getCookies(); // GWaller 13/4/11 IssueID #771 Client may not have sent any cookies - check for null if (cookies != null){ for (Cookie c: cookies){ if (c.getName().equals(redirectCookieName)){ redirectCookie = c; break; } } } return redirectCookie; } // END GWaller 6/10/10 IssueID #303 Support for multiple licence options /** * Is there a currently interuppted request? * * @param objectModel The Cocoon object Model */ public static boolean isInterupptedRequest(Map objectModel) { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); HttpSession session = request.getSession(); Object interruptedObject = session.getAttribute(REQUEST_INTERRUPTED); if (interruptedObject instanceof RequestInfo) { // There is currently either an interrupted or yet-to-be resumed request. return true; } // START GWaller 6/10/10 IssueID #303 Support for multiple licence options // Check to see if we have a redirect in a cookie Cookie redirectCookie = getRedirectCookie(request); if (redirectCookie != null){ return true; } // END GWaller 6/10/10 IssueID #303 Support for multiple licence options // There are not interupted requests. return false; } // GWaller IssueID #303 4/10/10 Add method to store the originally requested URL in a cookie so that it can be retrieved after the user authenticates (different to isInterupptedRequest // as it doesn't use the current session. This is required as sometimes the current session may not be the session the user ends up using after they log // in e.g. in the cause of the EdiAuth authentication method. /** * Method to store the originally requested URL in a cookie so that it can be retrieved after the user authenticates (different to isInterupptedRequest * as it doesn't use the current session. This is required as sometimes the current session may not be the session the user ends up using after they log * in e.g. in the cause of the EdiAuth authentication method. * * @param objectModel The Cocoon object Model */ public static void saveRequestedUrlInCookie(Map objectModel){ final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); final HttpServletResponse response = (HttpServletResponse) objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT); StringBuffer sb = new StringBuffer(50); String contextPath = request.getContextPath(); String servletPath = request.getServletPath(); String pathInfo = request.getPathInfo(); String queryString = request.getQueryString(); // NOTE: deliberately not including the contextPath! // Reason: All the AuthenticationAction code automatically appends the path which was saved to the context path sb.append(servletPath); if (pathInfo != null) { sb.append(pathInfo); } if (queryString != null) { sb.append("?"); sb.append(queryString); } Cookie cookie = new Cookie(getRedirectCookieName(), sb.toString()); cookie.setPath(contextPath); // GWaller 13/1/11 Make sue we set an expiry date on the cookie so that it doesn't hang around for long cookie.setMaxAge(ConfigurationManager.getIntProperty(COOKIE_REDIRECT_MAX_AGE_CONFIG_KEY, -1)); response.addCookie(cookie); } /** * Interrupt the current request and store if for later resumption. This request will * send an http redirect telling the client to authenticate first. Once that has been finished * then the request can be resumed. * * @param objectModel The Cocoon object Model * @param header A message header (i18n tag) * @param message A message for why the request was interrupted (i18n tag) * @param characters An untranslated messsage, perhaps an error message? */ public static void interruptRequest(Map objectModel, String header, String message, String characters) { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); HttpSession session = request.getSession(); // Store this interrupted request untill after the user successfully authenticates. RequestInfo interruptedRequest = new RequestInfo(request); // Set the request as interrupted session.setAttribute(REQUEST_INTERRUPTED,interruptedRequest); session.setAttribute(REQUEST_RESUME, null); // just to be clear. // Set the interrupt message session.setAttribute(REQUEST_INTERRUPTED_HEADER, header); session.setAttribute(REQUEST_INTERRUPTED_MESSAGE, message); session.setAttribute(REQUEST_INTERRUPTED_CHARACTERS, characters); // START GWaller 6/10/10 IssueID #303 Support for multiple licence options // Save the requested url in a cookie saveRequestedUrlInCookie(objectModel); } /** * Set the interrupted request to a resumable state. The * next request that the server recieves (for this session) that * has the same servletPath will be replaced with the previously * inturrupted request. * * @param objectModel The Cocoon object Model * @return */ public static String resumeInterruptedRequest(Map objectModel) { final HttpServletRequest request = (HttpServletRequest) objectModel.get(HttpEnvironment.HTTP_REQUEST_OBJECT); final HttpServletResponse response = (HttpServletResponse) objectModel.get(HttpEnvironment.HTTP_RESPONSE_OBJECT); HttpSession session = request.getSession(); // START GWaller 6/10/10 IssueID #303 Support for multiple licence options String cookieRedirect = null; // Check to see if we have a redirect in a cookie Cookie redirectCookie = getRedirectCookie(request); if (redirectCookie != null){ // Save the redirect string cookieRedirect = redirectCookie.getValue(); /* * ** Important ** * * Must delete the cookie so that the redirect only happens once */ // Set age to zero first redirectCookie.setMaxAge(0); // Make sure path is set so that it matches the cookie redirectCookie.setPath(request.getContextPath()); // Add cookie to response instance so that it is set in the browser and will delete the cookie response.addCookie(redirectCookie); } // END GWaller 6/10/10 IssueID #303 Support for multiple licence options // Clear the interrupt message session.setAttribute(AuthenticationUtil.REQUEST_INTERRUPTED_HEADER, null); session.setAttribute(AuthenticationUtil.REQUEST_INTERRUPTED_MESSAGE, null); session.setAttribute(AuthenticationUtil.REQUEST_INTERRUPTED_CHARACTERS, null); // Set the request as interrupted Object interruptedObject = session.getAttribute(REQUEST_INTERRUPTED); if (interruptedObject instanceof RequestInfo) { RequestInfo interruptedRequest = (RequestInfo) interruptedObject; session.setAttribute(REQUEST_INTERRUPTED, null); session.setAttribute(REQUEST_RESUME, interruptedRequest); // Return the path for which this request belongs too. Only urls // for this path may be resumed. return interruptedRequest.getServletPath(); } // GWaller 6/10/10 IssueID #303 Support for multiple licence options return cookieRedirect; } /** * Check to see if this request should be resumed. * * @param realHttpRequest The current real request * @return Either the current real request or a stored request that was previously interrupted. */ public static HttpServletRequest resumeRequest(HttpServletRequest realHttpRequest) { // First check to see if there is a resumed request. HttpSession session = realHttpRequest.getSession(); //session.setMaxInactiveInterval(60); Object object = session.getAttribute(REQUEST_RESUME); // Next check to make sure it's the right type of object, // there should be no condition where it is not - but always // safe to check. if (object instanceof RequestInfo) { RequestInfo interruptedRequest = (RequestInfo) object; // Next, check to make sure this real request if for the same url // path, if so then resume the previous request. String interruptedServletPath = interruptedRequest.getServletPath(); String realServletPath = realHttpRequest.getServletPath(); if (realServletPath != null && realServletPath.equals(interruptedServletPath)) { // Clear the resumed request and send the request back to be resumed. session.setAttribute(REQUEST_INTERRUPTED, null); session.setAttribute(REQUEST_RESUME, null); return interruptedRequest.wrapRequest(realHttpRequest); } } // Otherwise return the real request. return realHttpRequest; } }