/*
* LDAPAuthentication.java
*
* Version: $Revision: 3735 $
*
* Date: $Date: 2009-04-24 04:05:53 +0000 (Fri, 24 Apr 2009) $
*
* 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 java.util.Hashtable;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttribute;
import javax.naming.directory.BasicAttributes;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import javax.naming.directory.SearchResult;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.core.ConfigurationManager;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
/**
* Authentication module to authenticate against a flat LDAP tree where
* all users are in the same unit.
*
* @author Larry Stone, Stuart Lewis
* @version $Revision: 3735 $
*/
public class LDAPAuthentication
implements AuthenticationMethod {
/** log4j category */
private static Logger log = Logger.getLogger(LDAPAuthentication.class);
/**
* Let a real auth method return true if it wants.
*/
public boolean canSelfRegister(Context context,
HttpServletRequest request,
String username)
throws SQLException
{
// XXX might also want to check that username exists in LDAP.
return ConfigurationManager.getBooleanProperty("webui.ldap.autoregister");
}
/**
* Nothing here, initialization is done when auto-registering.
*/
public void initEPerson(Context context, HttpServletRequest request,
EPerson eperson)
throws SQLException
{
// XXX should we try to initialize netid based on email addr,
// XXX for eperson created by some other method??
}
/**
* Cannot change LDAP password through dspace, right?
*/
public boolean allowSetPassword(Context context,
HttpServletRequest request,
String username)
throws SQLException
{
// XXX is this right?
return false;
}
/*
* This is an explicit method.
*/
public boolean isImplicit()
{
return false;
}
/*
* Add authenticated users to the group defined in dspace.cfg by
* the ldap.login.specialgroup key.
*/
public int[] getSpecialGroups(Context context, HttpServletRequest request)
{
// Prevents anonymous users from being added to this group, and the second check
// ensures they are LDAP users
try
{
if (!context.getCurrentUser().getNetid().equals(""))
{
String groupName = ConfigurationManager.getProperty("ldap.login.specialgroup");
if ((groupName != null) && (!groupName.trim().equals("")))
{
Group ldapGroup = Group.findByName(context, groupName);
if (ldapGroup == null)
{
// Oops - the group isn't there.
log.warn(LogManager.getHeader(context,
"ldap_specialgroup",
"Group defined in ldap.login.specialgroup does not exist"));
return new int[0];
} else
{
return new int[] { ldapGroup.getID() };
}
}
}
}
catch (Exception npe) {
// The user is not an LDAP user, so we don't need to worry about them
}
return new int[0];
}
/*
* MIT policy on certs and groups, so always short-circuit.
*
* @return One of:
* SUCCESS, BAD_CREDENTIALS, CERT_REQUIRED, NO_SUCH_USER, BAD_ARGS
*/
public int authenticate(Context context,
String netid,
String password,
String realm,
HttpServletRequest request)
throws SQLException
{
log.info(LogManager.getHeader(context, "auth", "attempting trivial auth of user="+netid));
// Skip out when no netid or password is given.
if (netid == null || password == null)
return BAD_ARGS;
// Locate the eperson
EPerson eperson = null;
try
{
eperson = EPerson.findByNetid(context, netid.toLowerCase());
}
catch (SQLException e)
{
}
boolean loggedIn = false;
SpeakerToLDAP ldap = new SpeakerToLDAP(log);
// if they entered a netid that matches an eperson
if (eperson != null)
{
// e-mail address corresponds to active account
if (eperson.getRequireCertificate())
return CERT_REQUIRED;
else if (!eperson.canLogIn())
return BAD_ARGS;
{
if (ldap.ldapAuthenticate(netid, password, context))
{
context.setCurrentUser(eperson = EPerson.findByNetid(context, netid.toLowerCase()));
log.info(LogManager
.getHeader(context, "authenticate", "type=ldap"));
return SUCCESS;
}
else
return BAD_CREDENTIALS;
}
}
// the user does not already exist so try and authenticate them
// with ldap and create an eperson for them
else
{
if (ldap.ldapAuthenticate(netid, password, context))
{
// Register the new user automatically
log.info(LogManager.getHeader(context,
"autoregister", "netid=" + netid));
if ((ldap.ldapEmail!=null)&&(!ldap.ldapEmail.equals("")))
{
try
{
eperson = EPerson.findByEmail(context, ldap.ldapEmail);
if (eperson!=null)
{
log.info(LogManager.getHeader(context,
"type=ldap-login", "type=ldap_but_already_email"));
context.setIgnoreAuthorization(true);
eperson.setNetid(netid.toLowerCase());
eperson.update();
context.commit();
context.setIgnoreAuthorization(false);
context.setCurrentUser(eperson);
return SUCCESS;
}
else
{
if (canSelfRegister(context, request, netid))
{
// TEMPORARILY turn off authorisation
try
{
context.setIgnoreAuthorization(true);
eperson = EPerson.create(context);
if ((ldap.ldapEmail!=null)&&(!ldap.ldapEmail.equals(""))) eperson.setEmail(ldap.ldapEmail);
else eperson.setEmail(netid);
if ((ldap.ldapGivenName!=null)&&(!ldap.ldapGivenName.equals(""))) eperson.setFirstName(ldap.ldapGivenName);
if ((ldap.ldapSurname!=null)&&(!ldap.ldapSurname.equals(""))) eperson.setLastName(ldap.ldapSurname);
if ((ldap.ldapPhone!=null)&&(!ldap.ldapPhone.equals(""))) eperson.setMetadata("phone", ldap.ldapPhone);
eperson.setNetid(netid.toLowerCase());
eperson.setCanLogIn(true);
AuthenticationManager.initEPerson(context, request, eperson);
eperson.update();
context.commit();
context.setCurrentUser(eperson);
}
catch (AuthorizeException e)
{
return NO_SUCH_USER;
}
finally
{
context.setIgnoreAuthorization(false);
}
log.info(LogManager.getHeader(context, "authenticate",
"type=ldap-login, created ePerson"));
return SUCCESS;
}
else
{
// No auto-registration for valid certs
log.info(LogManager.getHeader(context,
"failed_login", "type=ldap_but_no_record"));
return NO_SUCH_USER;
}
}
}
catch (AuthorizeException e)
{
eperson = null;
}
finally
{
context.setIgnoreAuthorization(false);
}
}
}
}
return BAD_ARGS;
}
/**
* Internal class to manage LDAP query and results, mainly
* because there are multiple values to return.
*/
public class SpeakerToLDAP {
private Logger log = null;
/** ldap email result */
protected String ldapEmail = null;
/** ldap name result */
protected String ldapGivenName = null;
protected String ldapSurname = null;
protected String ldapPhone = null;
SpeakerToLDAP(Logger thelog)
{
log = thelog;
}
/**
* contact the ldap server and attempt to authenticate
*/
protected boolean ldapAuthenticate(String netid, String password, Context context)
{
if (!password.equals(""))
{
String ldap_provider_url = ConfigurationManager.getProperty("ldap.provider_url");
String ldap_id_field = ConfigurationManager.getProperty("ldap.id_field");
String ldap_search_context = ConfigurationManager.getProperty("ldap.search_context");
String ldap_object_context = ConfigurationManager.getProperty("ldap.object_context");
// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(javax.naming.Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(javax.naming.Context.PROVIDER_URL, ldap_provider_url);
// Authenticate
env.put(javax.naming.Context.SECURITY_AUTHENTICATION, "simple");
env.put(javax.naming.Context.SECURITY_PRINCIPAL, ldap_id_field+"="+netid+","+ldap_object_context);
env.put(javax.naming.Context.SECURITY_CREDENTIALS, password);
DirContext ctx = null;
try
{
// Create initial context
ctx = new InitialDirContext(env);
String ldap_email_field = ConfigurationManager.getProperty("ldap.email_field");
String ldap_givenname_field = ConfigurationManager.getProperty("ldap.givenname_field");
String ldap_surname_field = ConfigurationManager.getProperty("ldap.surname_field");
String ldap_phone_field = ConfigurationManager.getProperty("ldap.phone_field");
Attributes matchAttrs = new BasicAttributes(true);
matchAttrs.put(new BasicAttribute(ldap_id_field, netid));
String attlist[] = {ldap_email_field, ldap_givenname_field, ldap_surname_field, ldap_phone_field};
// look up attributes
try
{
NamingEnumeration answer = ctx.search(ldap_search_context, matchAttrs, attlist);
while(answer.hasMore()) {
SearchResult sr = (SearchResult)answer.next();
Attributes atts = sr.getAttributes();
Attribute att;
if (attlist[0]!=null)
{
att = atts.get(attlist[0]);
if (att != null) ldapEmail = (String)att.get();
}
if (attlist[1]!=null)
{
att = atts.get(attlist[1]);
if (att != null) ldapGivenName = (String)att.get();
}
if (attlist[2]!=null)
{
att = atts.get(attlist[2]);
if (att != null) ldapSurname = (String)att.get();
}
if (attlist[3]!=null)
{
att = atts.get(attlist[3]);
if (att != null) ldapPhone = (String)att.get();
}
}
}
catch (NamingException e)
{
// if the lookup fails go ahead and create a new record for them because the authentication
// succeeded
log.warn(LogManager.getHeader(context,
"ldap_attribute_lookup", "type=failed_search "+e));
return true;
}
}
catch (NamingException e)
{
log.warn(LogManager.getHeader(context,
"ldap_authentication", "type=failed_auth "+e));
return false;
}
finally
{
// Close the context when we're done
try
{
if (ctx != null)
ctx.close();
}
catch (NamingException e)
{
}
}
}
else
{
return false;
}
return true;
}
}
/*
* Returns URL 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
*/
public String loginPageURL(Context context,
HttpServletRequest request,
HttpServletResponse response)
{
return response.encodeRedirectURL(request.getContextPath() +
"/ldap-login");
}
/**
* Returns message key for title of the "login" page, to use
* in a menu showing the choice of multiple login methods.
*
* @param context
* DSpace context, will be modified (ePerson set) upon success.
*
* @return Message key to look up in i18n message catalog.
*/
public String loginPageTitle(Context context)
{
return "org.dspace.eperson.LDAPAuthentication.title";
}
}