mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-07 10:04:21 +00:00

git-svn-id: http://scm.dspace.org/svn/repo/dspace/trunk@5565 9c30dcfa-912a-0410-8fc2-9e0234be79fd
452 lines
16 KiB
Java
452 lines
16 KiB
Java
/*
|
|
* ShibAuthentication.java
|
|
*
|
|
* Version: $Revision$
|
|
*
|
|
* Copyright (c) 2002-2009, The DSpace Foundation. 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 DSpace Foundation nor the names of its
|
|
* 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.authenticate;
|
|
|
|
import java.sql.SQLException;
|
|
import javax.servlet.http.HttpServletRequest;
|
|
import java.util.Collection;
|
|
import javax.servlet.http.HttpServletResponse;
|
|
|
|
import org.apache.log4j.Logger;
|
|
import org.dspace.authorize.AuthorizeException;
|
|
|
|
import org.dspace.core.Context;
|
|
import org.dspace.core.ConfigurationManager;
|
|
import org.dspace.core.LogManager;
|
|
import org.dspace.authenticate.AuthenticationManager;
|
|
import org.dspace.authenticate.AuthenticationMethod;
|
|
import org.dspace.eperson.EPerson;
|
|
import org.dspace.eperson.Group;
|
|
|
|
/**
|
|
* Shibboleth authentication for DSpace, tested on Shibboleth 1.3.x and
|
|
* Shibboleth 2.x. Read <a href=
|
|
* "https://mams.melcoe.mq.edu.au/zope/mams/pubs/Installation/dspace15/view"
|
|
* >Shib DSpace 1.5</a> for installation procedure. Read dspace.cfg for details
|
|
* on options available.
|
|
*
|
|
* @author <a href="mailto:bliong@melcoe.mq.edu.au">Bruc Liong, MELCOE</a>
|
|
* @author <a href="mailto:kli@melcoe.mq.edu.au">Xiang Kevin Li, MELCOE</a>
|
|
* @version $Revision$
|
|
*/
|
|
public class ShibAuthentication implements AuthenticationMethod
|
|
{
|
|
/** log4j category */
|
|
private static Logger log = Logger.getLogger(ShibAuthentication.class);
|
|
|
|
public int authenticate(Context context, String username, String password,
|
|
String realm, HttpServletRequest request) throws SQLException
|
|
{
|
|
if (request == null)
|
|
{
|
|
return BAD_ARGS;
|
|
}
|
|
log.info("Shibboleth login started...");
|
|
|
|
java.util.Enumeration names = request.getHeaderNames();
|
|
String name;
|
|
while (names.hasMoreElements())
|
|
{
|
|
log.debug("header:" + (name = names.nextElement().toString()) + "="
|
|
+ request.getHeader(name));
|
|
}
|
|
|
|
boolean isUsingTomcatUser = ConfigurationManager
|
|
.getBooleanProperty("authentication.shib.email-use-tomcat-remote-user");
|
|
String emailHeader = ConfigurationManager
|
|
.getProperty("authentication.shib.email-header");
|
|
String fnameHeader = ConfigurationManager
|
|
.getProperty("authentication.shib.firstname-header");
|
|
String lnameHeader = ConfigurationManager
|
|
.getProperty("authentication.shib.lastname-header");
|
|
|
|
String email = null;
|
|
String fname = null;
|
|
String lname = null;
|
|
|
|
if (emailHeader != null)
|
|
{
|
|
// try to grab email from the header
|
|
email = request.getHeader(emailHeader);
|
|
|
|
// fail, try lower case
|
|
if (email == null)
|
|
email = request.getHeader(emailHeader.toLowerCase());
|
|
}
|
|
|
|
// try to pull the "REMOTE_USER" info instead of the header
|
|
if (email == null && isUsingTomcatUser)
|
|
{
|
|
email = request.getRemoteUser();
|
|
log.info("RemoteUser identified as: " + email);
|
|
}
|
|
|
|
// No email address, perhaps the eperson has been setup, better check it
|
|
if (email == null)
|
|
{
|
|
EPerson p = context.getCurrentUser();
|
|
if (p != null)
|
|
email = p.getEmail();
|
|
}
|
|
|
|
if (email == null)
|
|
{
|
|
log
|
|
.error("No email is given, you're denied access by Shib, please release email address");
|
|
return AuthenticationMethod.BAD_ARGS;
|
|
}
|
|
|
|
email = email.toLowerCase();
|
|
|
|
if (fnameHeader != null)
|
|
{
|
|
// try to grab name from the header
|
|
fname = request.getHeader(fnameHeader);
|
|
|
|
// fail, try lower case
|
|
if (fname == null)
|
|
fname = request.getHeader(fnameHeader.toLowerCase());
|
|
}
|
|
if (lnameHeader != null)
|
|
{
|
|
// try to grab name from the header
|
|
lname = request.getHeader(lnameHeader);
|
|
|
|
// fail, try lower case
|
|
if (lname == null)
|
|
lname = request.getHeader(lnameHeader.toLowerCase());
|
|
}
|
|
|
|
// future version can offer auto-update feature, this needs testing
|
|
// before inclusion to core code
|
|
|
|
EPerson eperson = null;
|
|
try
|
|
{
|
|
eperson = EPerson.findByEmail(context, email);
|
|
context.setCurrentUser(eperson);
|
|
}
|
|
catch (AuthorizeException e)
|
|
{
|
|
log.warn("Fail to locate user with email:" + email, e);
|
|
eperson = null;
|
|
}
|
|
|
|
// auto create user if needed
|
|
if (eperson == null
|
|
&& ConfigurationManager
|
|
.getBooleanProperty("authentication.shib.autoregister"))
|
|
{
|
|
log.info(LogManager.getHeader(context, "autoregister", "email="
|
|
+ email));
|
|
|
|
// TEMPORARILY turn off authorisation
|
|
context.setIgnoreAuthorization(true);
|
|
try
|
|
{
|
|
eperson = EPerson.create(context);
|
|
eperson.setEmail(email);
|
|
if (fname != null)
|
|
eperson.setFirstName(fname);
|
|
if (lname != null)
|
|
eperson.setLastName(lname);
|
|
eperson.setCanLogIn(true);
|
|
AuthenticationManager.initEPerson(context, request, eperson);
|
|
eperson.update();
|
|
context.commit();
|
|
context.setCurrentUser(eperson);
|
|
}
|
|
catch (AuthorizeException e)
|
|
{
|
|
log.warn("Fail to authorize user with email:" + email, e);
|
|
eperson = null;
|
|
}
|
|
finally
|
|
{
|
|
context.setIgnoreAuthorization(false);
|
|
}
|
|
}
|
|
|
|
if (eperson == null)
|
|
{
|
|
return AuthenticationMethod.NO_SUCH_USER;
|
|
}
|
|
else
|
|
{
|
|
// the person exists, just return ok
|
|
context.setCurrentUser(eperson);
|
|
request.getSession().setAttribute("shib.authenticated",
|
|
Boolean.TRUE);
|
|
}
|
|
|
|
return AuthenticationMethod.SUCCESS;
|
|
}
|
|
|
|
/**
|
|
* Grab the special groups to be automatically provisioned for the current
|
|
* user. Currently the mapping for the groups is done one-to-one, future
|
|
* version can consider the usage of regex for such mapping.
|
|
*/
|
|
public int[] getSpecialGroups(Context context, HttpServletRequest request)
|
|
{
|
|
// no user logged in or user not logged from shibboleth
|
|
if (request == null || context.getCurrentUser() == null
|
|
|| request.getSession().getAttribute("shib.authenticated") == null)
|
|
{
|
|
return new int[0];
|
|
}
|
|
|
|
if (request.getSession().getAttribute("shib.specialgroup") != null)
|
|
{
|
|
return (int[]) request.getSession().getAttribute(
|
|
"shib.specialgroup");
|
|
}
|
|
|
|
java.util.Set groups = new java.util.HashSet();
|
|
String roleHeader = ConfigurationManager
|
|
.getProperty("authentication.shib.role-header");
|
|
boolean roleHeader_ignoreScope = ConfigurationManager
|
|
.getBooleanProperty("authentication.shib.role-header.ignore-scope");
|
|
if (roleHeader == null || roleHeader.trim().length() == 0)
|
|
roleHeader = "Shib-EP-UnscopedAffiliation"; // fall back to default
|
|
String affiliations = request.getHeader(roleHeader);
|
|
|
|
// try again with all lower case...maybe has better luck
|
|
if (affiliations == null)
|
|
affiliations = request.getHeader(roleHeader.toLowerCase());
|
|
|
|
// default role when fully authN but not releasing any roles?
|
|
String defaultRoles = ConfigurationManager
|
|
.getProperty("authentication.shib.default-roles");
|
|
if (affiliations == null && defaultRoles != null)
|
|
{
|
|
affiliations = defaultRoles;
|
|
}
|
|
|
|
if (affiliations != null)
|
|
{
|
|
java.util.StringTokenizer st = new java.util.StringTokenizer(
|
|
affiliations, ";,");
|
|
// do the mapping here
|
|
while (st.hasMoreTokens())
|
|
{
|
|
String affiliation = st.nextToken().trim();
|
|
|
|
// strip scope if present and roleHeader_ignoreScope
|
|
if (roleHeader_ignoreScope)
|
|
{
|
|
int index = affiliation.indexOf('@');
|
|
if (index != -1) affiliation = affiliation.substring(0,index);
|
|
}
|
|
|
|
// perform mapping here if necessary
|
|
String groupLabels = ConfigurationManager
|
|
.getProperty("authentication.shib.role." + affiliation);
|
|
if (groupLabels == null || groupLabels.trim().length() == 0)
|
|
groupLabels = ConfigurationManager
|
|
.getProperty("authentication.shib.role."
|
|
+ affiliation.toLowerCase());
|
|
|
|
// revert back to original entry when no mapping is provided
|
|
if (groupLabels == null)
|
|
{
|
|
groupLabels = affiliation;
|
|
}
|
|
|
|
String[] labels = groupLabels.split(",");
|
|
for (int i = 0; i < labels.length; i++)
|
|
{
|
|
addGroup(groups, context, labels[i].trim());
|
|
}
|
|
}
|
|
}
|
|
|
|
int ids[] = new int[groups.size()];
|
|
java.util.Iterator it = groups.iterator();
|
|
for (int i = 0; it.hasNext(); i++)
|
|
{
|
|
ids[i] = ((Integer) it.next()).intValue();
|
|
}
|
|
|
|
// store the special group, if already transformed from headers
|
|
// since subsequent header may not have the values anymore
|
|
if (ids.length != 0)
|
|
{
|
|
request.getSession().setAttribute("shib.specialgroup", ids);
|
|
}
|
|
|
|
return ids;
|
|
}
|
|
|
|
/** Find dspaceGroup in DSpace database, if found, include it into groups */
|
|
private void addGroup(Collection groups, Context context, String dspaceGroup)
|
|
{
|
|
try
|
|
{
|
|
Group g = Group.findByName(context, dspaceGroup);
|
|
if (g == null)
|
|
{
|
|
// oops - no group defined
|
|
log.warn(LogManager.getHeader(context, dspaceGroup
|
|
+ " group is not found!! Admin needs to create one!",
|
|
"requiredGroup=" + dspaceGroup));
|
|
groups.add(Integer.valueOf(0));
|
|
}
|
|
else
|
|
{
|
|
groups.add(Integer.valueOf(g.getID()));
|
|
}
|
|
log.info("Mapping group: " + dspaceGroup + " to groupID: "
|
|
+ (g == null ? 0 : g.getID()));
|
|
}
|
|
catch (SQLException e)
|
|
{
|
|
log.error("Mapping group:" + dspaceGroup + " failed with error", e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Indicate whether or not a particular self-registering user can set
|
|
* themselves a password in the profile info form.
|
|
*
|
|
* @param context
|
|
* DSpace context
|
|
* @param request
|
|
* HTTP request, in case anything in that is used to decide
|
|
* @param email
|
|
* e-mail address of user attempting to register
|
|
*
|
|
*/
|
|
public boolean allowSetPassword(Context context,
|
|
HttpServletRequest request, String email) throws SQLException
|
|
{
|
|
// don't use password at all
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Predicate, is this an implicit authentication method. An implicit method
|
|
* gets credentials from the environment (such as an HTTP request or even
|
|
* Java system properties) rather than the explicit username and password.
|
|
* For example, a method that reads the X.509 certificates in an HTTPS
|
|
* request is implicit.
|
|
*
|
|
* @return true if this method uses implicit authentication.
|
|
*/
|
|
public boolean isImplicit()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Indicate whether or not a particular user can self-register, based on
|
|
* e-mail address.
|
|
*
|
|
* @param context
|
|
* DSpace context
|
|
* @param request
|
|
* HTTP request, in case anything in that is used to decide
|
|
* @param username
|
|
* e-mail address of user attempting to register
|
|
*
|
|
*/
|
|
public boolean canSelfRegister(Context context, HttpServletRequest request,
|
|
String username) throws SQLException
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Initialise a new e-person record for a self-registered new user.
|
|
*
|
|
* @param context
|
|
* DSpace context
|
|
* @param request
|
|
* HTTP request, in case it's needed
|
|
* @param eperson
|
|
* newly created EPerson record - email + information from the
|
|
* registration form will have been filled out.
|
|
*
|
|
*/
|
|
public void initEPerson(Context context, HttpServletRequest request,
|
|
EPerson eperson) throws SQLException
|
|
{
|
|
|
|
}
|
|
|
|
/**
|
|
* Get login page to which to redirect. Returns URL (as string) to which to
|
|
* redirect to obtain credentials (either password prompt or e.g. HTTPS port
|
|
* for client cert.); null means no redirect.
|
|
*
|
|
* @param context
|
|
* DSpace context, will be modified (ePerson set) upon success.
|
|
*
|
|
* @param request
|
|
* The HTTP request that started this operation, or null if not
|
|
* applicable.
|
|
*
|
|
* @param response
|
|
* The HTTP response from the servlet method.
|
|
*
|
|
* @return fully-qualified URL or null
|
|
*/
|
|
public String loginPageURL(Context context, HttpServletRequest request,
|
|
HttpServletResponse response)
|
|
{
|
|
return response.encodeRedirectURL(request.getContextPath()
|
|
+ "/shibboleth-login");
|
|
}
|
|
|
|
/**
|
|
* Get title of login page to which to redirect. Returns a <i>message
|
|
* key</i> that gets translated into the title or label for "login page" (or
|
|
* null, if not implemented) This title may be used to identify the link to
|
|
* the login page in a selection menu, when there are multiple ways to
|
|
* login.
|
|
*
|
|
* @param context
|
|
* DSpace context, will be modified (ePerson set) upon success.
|
|
*
|
|
* @return title text.
|
|
*/
|
|
public String loginPageTitle(Context context)
|
|
{
|
|
return "org.dspace.authenticate.ShibAuthentication.title";
|
|
}
|
|
}
|