DSpace / DSpace

(Official) The DSpace digital asset management system that powers your Institutional Repository
https://wiki.lyrasis.org/display/DSDOC7x/
BSD 3-Clause "New" or "Revised" License
845 stars 1.28k forks source link

[DS-50] LDAP+Active Directory authentication patch - ID: 2100378 #3428

Closed dspace-bot closed 14 years ago

dspace-bot commented 15 years ago

Imported from JIRA [DS-50] created by kipkorir2008

Thanks, Stuart -

I'll submit the patch to the queue.
You can grab a zip file that includes only the
changed files here:
http://erwg.lib.auburn.edu/dspace-ldap_20080828.zip

The main changes were:

*. Our A.D. setup does not allow anonymous bind,
and also has user info scattered around the LDAP tree.
To bind an arbitrary user, we pass a distinguished name
of:
DOMAIN\USER-ID
rather than some LDAP path like cn=USER&ou=People&dc=...
We detect this case by allowing the admin
to specify
windows_domain:domain-name
(something like that) as the LDAP object-context in dspace.cfg.

*. Since our user info is scattered through an LDAP tree -
I patched the "lookup user info" part of the code
to specify a recursive JNDI search.
Your last patch may have done the same thing.

*. We would like a user that successfully authenticates against A.D.
to automatically be given certain privileges.
I setup the getSpecialGroups function to accept a list of group-ids in
the
"ldap.dspace.autogroup"
dspace.cfg parameter.

*. I wanted to be able to run a standalone test case against the LDAP
authenticate and search code, so I split out the 'DataFromLDAP'
nested class to an external interface, and include a junit test case.
Also added a few lines to pom.xml to help run the test with verbose
logging, etc.

Cheers,
Reuben

>>> Stuart Lewis 9/6/2008 7:57 AM >>>
Hi Reuben,

Thanks for getting in touch. I¹ve got that email sitting in my inbox
waiting
for me to get around to replying to it - sorry it has taken a little
while.

What changes have you made to make it work with Active Directory?

It would be great if you could formally submit your patch to the DSpace
patch queue
(http://sourceforge.net/tracker/?atid=319984&group_id=19984&func=browse).
There are a few of us working on a generic LDAP authenticator which will
hopefully work with any AD / LDAP system, so it would be good to see what
changes you¹ve made to see if we can incorporate them too.

Thanks,

Stuart

On 28/08/2008 16:14, "Reuben Pasquini" wrote:

> Hello!
>
> I've put together a set of patches to the LDAPAuthentication
> code to get it working against Active Directory at Auburn
> University, support implicit-group member-ids in dspace.cfg,
> and add a JUnit regression test.
> I think the changes are backward compatable and generic,
> but I've only tested the code in my environment.
>
> I hope that we can check this patch into the dspace repository.
> An overview and svn diff follow, and a zip file with
> the modified files is available here:
> http://erwg.lib.auburn.edu/dspace-ldap_20080828.zip
> Please take a look, and let me know what you think.
>
> Cheers,
> Reuben
>
> -----------------------------------
>
> Changes under dspace-api org.dspace.authentication.
>
> *. Moved the
> LDAPAuthentication.SpeakerToLDAP
> nested class out to its own non-nested interface
> with a DefaultSpeakerToLDAP implementation.
>
> *. Refactored SpeakerToLDAP#ldapAuthenticate(...)
> to return a DataFromLDAP POJO data object
> rather than set object member variables.
>
> *. Implemented SpeakerToLDAPCase JUnit test-case
> and PackageTestSuite classes to support simple
> regression tests against SpeakerToLDAP implementations.
> Modified pom.xml so that
> 'mvn test'
> runs with a verbose log4j setting.
>
> *. Modified the way SpeakToLDAP handles the
> ldap.object_context
> dspace.cfg configuration property so that
> if the ldap.object_context matches
> 'WINDOWS_DOMAIN:DOMAIN_NAME',
> then LDAP attempts to bind with
> 'DOMAIN_NAME\NETID'
> rather than
> 'cn=NETID,ldap.object_context'
> . This change allows us to configure LDAP
> to bind with Active Directory out of the box.
>
> *. Modifed the LDAP search for user-info to take a SearchControls
> parameter that specifies a recursive tree-search under the
> ldap.search_context
> tree for a single user-object result.
> Once again - this allows LDAPAuthenticate to work
> with an Active Directory tree that has user objects
> organized into different folders under a tree.
>
> *. Modified LDAPAuthentication.getSpecialGroups
> to access the
> ldap.dspace.autogroup
> dspace.cfg configuration property
> to get the group-ids that an LDAP-authenticated
> user should be an implicit member of.
> This makes it easy to configure a system where
> every user that can authenticate can also
> submit material to some collections.
>
> *. Changed some of the if/else nesting in
> LDAPAuthentication.authenticate
> so that instead of having something like
> if ()

{

... return bla; } else {
we have
if () {> ...> return bla;> }

> ... // no else
and instead of
} else {
if ()

{ return goo }

> }
we just have
} else if () {
just to make the control flow a little
easier to look at.

----------------------------------

$ svn diff pom.xml src/test src/main/java/org/dspace/authenticate >
/tmp/bla

Index: pom.xml
===================================================================
— pom.xml (revision 2942)
+++ pom.xml (working copy)
@@ -61,6 +61,7 @@


http://dspace.svn.sourceforge.net/viewvc/dspace/branches/dspace-1_5_x/
dsp
ace



+

@@ -188,6 +189,23 @@
com.ibm.icu
icu4j

+
+ junit
+ junit
+ 3.8.1
+ test
+



-
\ No newline at end of file
+
+
+
+ src/test/resources
+
+ log4j.properties
+

+

+

+

+
+
Index: src/test/java/org/dspace/authenticate/SpeakerToLDAPCase.java
===================================================================

src/test/java/org/dspace/authenticate/SpeakerToLDAPCase.java (revision
0)
+++
src/test/java/org/dspace/authenticate/SpeakerToLDAPCase.java (revision
0)
@@ -0,0 +1,56 @@
+package org.dspace.authenticate;
+
+
+import junit.framework.TestCase;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Generic test runner for SpeakerToLDAP implementations.
+ */
+public class SpeakerToLDAPCase extends TestCase {
+ private static final Logger olog = Logger.getLogger(
SpeakerToLDAPCase.class );
+
+ private SpeakerToLDAP oldap;
+ private String os_netid;
+ private String os_password;
+
+
+ /**
+ * Inject the test dependencies - initializes test properties
+ *
+ * @param s_name of test - pass to super
+ * @param ldap instance to authenticate agains
+ * @param s_netid user-id to authenticate as
+ * @param s_password for s_netid
+ */
+ public SpeakerToLDAPCase ( String s_name, SpeakerToLDAP ldap,
+ String s_netid, String s_password
+ )

{

  • super( s_name );
  • oldap = ldap;
  • os_netid = s_netid;
  • os_password = s_password;
  • }

    > +

  • /**
    • Try to authenticate against the constructor-supplied
    • (SpeakerToLDAP, s_netid, s_password)
  • */
  • public void testAuthenticate() {
  • try {

  • DataFromLDAP ldap_info = oldap.ldapAuthenticate( os_netid, os_password );
  • assertTrue( "Test user logged in ok: " + os_netid,
  • null != ldap_info
  • );
  • // need e-mail to key into eperson database
  • olog.info( "Got e-mail for " + os_netid + ": " + ldap_info.getEmail () );
  • assertTrue( "Got e-mail info for " + os_netid + " from ldap",
  • null != ldap_info.getEmail ()
  • );
  • }

    catch ( Exception e )

    {

  • olog.info( "Failed to authenticate user: " + os_netid, e );
  • assertTrue( "Failed to authenticate user: " + os_netid + ", caught: " + e, false );
  • }

    > + }
    +}
    Index: src/test/java/org/dspace/authenticate/PackageTest.java
    ===================================================================

    src/test/java/org/dspace/authenticate/PackageTest.java (revision
    0)
    +++
    src/test/java/org/dspace/authenticate/PackageTest.java (revision
    0)
    @@ -0,0 +1,40 @@
    +package org.dspace.authenticate;
    +
    +
    +import junit.framework.TestCase;
    +import junit.framework.TestSuite;
    +
    +import org.apache.log4j.Logger;
    +
    +/**

    • Specialization of AbstactSpeakerToLDAPTest configured
    • to run a DefaultSpeakerToLDAPTest through a test.
  • */
    +public class PackageTest extends TestCase {
  • private static final Logger olog = Logger.getLogger(
    PackageTest.class );
    +
    +
  • /**
    • Build up the batch of tests to run for the
      org.dspace.authenticate
    • package. You'll have to modify the properties injected into
      the
    • test SpeakerToLDAP to work with your environment
  • */
  • public static TestSuite suite () {

  • TestSuite suite = new TestSuite( PackageTest.class.getName () );
  • SpeakerToLDAP ldap = new DefaultSpeakerToLDAP ( "ldap://ldaps.university.edu",
  • "cn",
  • "OU=People,OU=AUMain,DC=auburn,DC=edu",

  • "WINDOWS_DOMAIN:AUBURN",

  • "mail",
  • "givenName",
  • "sn",
  • "telephoneNumber"

  • );
  • suite.addTest ( new SpeakerToLDAPCase( "testAuthenticate", ldap,
  • "USER", "PASSWORD"
  • )
  • );
  • return suite;
  • }

    > +
    +}
    Index: src/test/resources/log4j.properties
    ===================================================================
    — src/test/resources/log4j.properties (revision 0)
    +++ src/test/resources/log4j.properties (revision 0)
    @@ -0,0 +1,9 @@
    +# Set root logger level to DEBUG and its only appender to A1.
    +log4j.rootLogger=DEBUG, A1
    +
    +# A1 is set to be a ConsoleAppender.
    +log4j.appender.A1=org.apache.log4j.ConsoleAppender
    +
    +# A1 uses PatternLayout.
    +log4j.appender.A1.layout=org.apache.log4j.PatternLayout
    +log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x -
    %m%n
    Index: src/main/java/org/dspace/authenticate/LDAPAuthentication.java
    ===================================================================

    src/main/java/org/dspace/authenticate/LDAPAuthentication.java (revision
    3054)
    +++
    src/main/java/org/dspace/authenticate/LDAPAuthentication.java (working
    copy)
    @@ -40,17 +40,11 @@
    package org.dspace.authenticate;

    import java.sql.SQLException;
    +import java.util.ArrayList;
    +import java.util.List;
    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;

    @@ -60,6 +54,7 @@
    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
    @@ -72,9 +67,66 @@
    implements AuthenticationMethod {

    /* log4j category /
  • private static Logger log =
    Logger.getLogger(LDAPAuthentication.class);
  • private static final Logger olog =
    Logger.getLogger(LDAPAuthentication.class);

  • private final SpeakerToLDAP oldap;
    +
    /**
    • Constructor injects SpeakerToLDAP dependency
  • *
    • @param ldap SpeakerToLDAP knows how to authenticate
    • against and query the LDAP directory
    • @param b_autoregister set true to auto-register an eperson
    • for a new user that succesfully authenticates
    • with LDAP
    • @param v_special_group ids of user groups that an
      LDAP-authenticated
    • user should be considered an implicit member of
  • */
  • public LDAPAuthentication ( SpeakerToLDAP ldap,
  • boolean b_autoregister,
  • int[] v_special_group
  • ) {

  • oldap = ldap;
  • ob_autoregister = b_autoregister;
  • ov_special_group = v_special_group;
  • }

    > +

  • /**
    • Constructor invoked by dspace.cfg based configuration
    • engine sets up DefaultSpeakerToLDAP,
    • checks ldap.autoregister and ldap.dspace.autogroup
    • configuration values to determine canSelfRegister
    • and getSpecialGroups property values.
  • */
  • public LDAPAuthentication () {
  • String s_groups =
    ConfigurationManager.getProperty("ldap.dspace.autogroup");
    +
  • List v_group_id = new ArrayList();
  • if ( null != s_groups ) {
  • String[] v_group_name = s_groups.trim ().split(
    ",
    s*" );
  • for ( int i=0; i < v_group_name.length; ++i ) {
  • String s_group = v_group_name[i].trim ();
  • if ( s_group.length () > 0 ) {
  • try {

  • v_group_id.add ( Integer.parseInt( s_group ) );
  • }

    catch ( Exception e )

    {

  • olog.warn( "Exception parsing group " + s_group, e );
  • }

    > + }

  • }
  • }
  • oldap = new DefaultSpeakerToLDAP ();
  • ob_autoregister =
    ConfigurationManager.getBooleanProperty("webui.ldap.autoregister");
  • ov_special_group = new int[ v_group_id.size () ];
  • int i_count = 0;
  • for ( Integer i_group_id : v_group_id ) {

  • ov_special_group[ i_count ] = i_group_id;
  • ++i_count;
  • }

    > + }
    +

  • private final boolean ob_autoregister;
  • /**
  • Let a real auth method return true if it wants.
    */
    public boolean canSelfRegister(Context context,
    @@ -83,8 +135,7 @@
    throws SQLException
    { // XXX might also want to check that username exists in LDAP.

  • return ConfigurationManager.getBooleanProperty("webui.ldap.autoregister");
  • return ob_autoregister; }

    >
    /**
    @@ -118,12 +169,10 @@
    return false;
    }

  • /*
    • Nothing here.
  • */
  • private final int[] ov_special_group;
    public int[] getSpecialGroups(Context context, HttpServletRequest
    request)
    {

  • return new int[0];
  • return ov_special_group; }

    >
    /*
    @@ -139,261 +188,140 @@
    HttpServletRequest request)
    throws SQLException
    {

  • log.info(LogManager.getHeader(context, "auth", "attempting
    trivial auth of user="+netid));
  • olog.info(LogManager.getHeader(context, "auth", "attempting
    auth of user="+netid));

    // Skip out when no netid or password is given.
  • if (netid == null || password == null)
  • return BAD_ARGS;
  • if (netid == null || password == null) {

  • return BAD_ARGS;
  • }

    >
    // Locate the eperson

  • EPerson eperson = null;
  • EPerson eperson = null;
    try
    {

  • eperson = EPerson.findByNetid(context, netid.toLowerCase());
  • eperson = EPerson.findByNetid(context, netid.toLowerCase()); }

    > catch (SQLException e)

    { }

    > - boolean loggedIn = false;

  • SpeakerToLDAP ldap = new SpeakerToLDAP(log);

  • olog.debug( "Found eperson for " + netid );
    +
    // if they entered a netid that matches an eperson
    if (eperson != null)
    {
    // e-mail address corresponds to active account
  • if (eperson.getRequireCertificate())
  • if (eperson.getRequireCertificate()) { return CERT_REQUIRED;

  • else if (!eperson.canLogIn())
  • }

    else if (!eperson.canLogIn()) {
    return BAD_ARGS;

  • {
  • if (ldap.ldapAuthenticate(netid, password, context))
  • {

  • }

    > + try {

  • // authenticate
  • olog.debug( "Attempting LDAP auth-1 for " + netid );
  • DataFromLDAP ldap_info = oldap.ldapAuthenticate(
    netid, password );
  • if ( null != ldap_info ) { context.setCurrentUser(eperson = EPerson.findByNetid(context, netid.toLowerCase()));

  • log.info(LogManager
  • .getHeader(context, "authenticate", "type=ldap"));
  • olog.info(LogManager
  • .getHeader(context, "authenticate", "type=ldap")); return SUCCESS;
  • }

    > - else

  • return BAD_CREDENTIALS;
  • }
  • } catch ( NamingException e ) {

  • olog.warn( "Failed to authenticate user: " + netid, e ); }

    > + //else

    {

  • return BAD_CREDENTIALS;
  • }

    > + // eperson == null

  • if ( null != eperson ) {

  • throw new AssertionError( "eperson should be null here!" ); }

    > -

  • // 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);
  • 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);
  • 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);
  • }

    > - }

  • }
  • olog.debug( "Attempting LDAP auth-2 for " + netid );
  • DataFromLDAP ldap_info = null;
  • try {

  • ldap_info = oldap.ldapAuthenticate( netid, password );
  • }

    catch ( NamingException e )

    {

  • olog.warn( "Failed to authenticate user: " + netid, e ); }

    > - 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;
  • if ( (null == ldap_info)
  • || (ldap_info.getEmail()==null)
  • || ldap_info.getEmail().equals("")
  • ) {

  • return BAD_ARGS; // failed to authenticate or get e-mail address }

    >

  • /**
    • 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();
  • }

    > - }

  • //
  • // autoregister the ldap-authenticated user
  • //
  • olog.info(LogManager.getHeader(context,
  • "autoregister", "netid=" +
    netid)
  • );
    +
  • try {
  • eperson = EPerson.findByEmail(context,
    ldap_info.getEmail());
  • if (eperson!=null) {

  • // Just need to set the netid on the eperson record
  • olog.info(LogManager.getHeader(context,
  • "type=ldap-login", "type=ldap_but_already_email"));
  • context.setIgnoreAuthorization(true);
  • eperson.setNetid(netid);
  • 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);
  • eperson.setEmail(ldap_info.getEmail());
  • if ((ldap_info.getGivenName()!=null)
  • &&(!ldap_info.getGivenName().equals(""))
  • ) {

  • eperson.setFirstName(ldap_info.getGivenName()); }

    > - 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;
  • if ((ldap_info.getSurname()!=null)
  • &&(!ldap_info.getSurname().equals(""))
  • ) {

  • eperson.setLastName(ldap_info.getSurname()); }

    > - }

  • 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();
  • if ((ldap_info.getPhone()!=null)
  • &&(!ldap_info.getPhone().equals(""))
  • ) {

  • eperson.setMetadata("phone", ldap_info.getPhone()); }

    > - catch (NamingException e)

  • {

  • }

    > + eperson.setNetid(ldap_info.getNetId());

  • 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); }
  • olog.info(LogManager.getHeader(context,
    "authenticate",
  • "type=ldap-login,
    created ePerson"));
  • return SUCCESS;
  • } else {
  • // No auto-registration for valid certs
  • olog.info(LogManager.getHeader(context,
  • "failed_login", "type=ldap_but_no_record"));
  • return NO_SUCH_USER; }
  • else
  • {
  • return false;
  • }
    -
  • return true;
  • } catch (AuthorizeException e) {
  • eperson = null;
  • // authentication failed
  • return BAD_ARGS;
  • } finally {> + context.setIgnoreAuthorization(false);> }

    > -
    -

  • // Unreachable!
    }

    +
    /*
  • Returns URL to which to redirect to obtain credentials (either
    password
  • prompt or e.g. HTTPS port for client cert.); null means no
    redirect.
    @@ -430,4 +358,4 @@
    { return "org.dspace.eperson.LDAPAuthentication.title"; }

    > -}
    \ No newline at end of file
    +}
    Index: src/main/java/org/dspace/authenticate/DefaultSpeakerToLDAP.java
    ===================================================================

    src/main/java/org/dspace/authenticate/DefaultSpeakerToLDAP.java
    (revision
    0)
    +++
    src/main/java/org/dspace/authenticate/DefaultSpeakerToLDAP.java
    (revision
    0)
    @@ -0,0 +1,183 @@
    +package org.dspace.authenticate;
    +
    +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.SearchControls;
    +import javax.naming.directory.SearchResult;
    +
    +import org.apache.log4j.Logger;
    +import org.dspace.core.ConfigurationManager;
    +
    +/**

    • Internal class to manage LDAP query and results, mainly
    • because there are multiple values to return.
  • */
    +public class DefaultSpeakerToLDAP implements SpeakerToLDAP {
  • private static final Logger olog =
    Logger.getLogger(DefaultSpeakerToLDAP.class);
  • private final String os_provider_url;
  • private final String os_id_field;
  • private final String os_search_context;
  • private final String os_object_context;
  • private final String os_email_field;
  • private final String os_givenname_field;
  • private final String os_surname_field;
  • private final String os_phone_field;
    +
    +
  • /**
    • Constructor allows injection of
    • configuration parameters.
  • *
    • @param s_provider_url to the server - we assume simple
      authentication
    • @param s_id_field attribute of user object - usually cn
    • @param s_search_context subtree under which to search for user
      info,
    • ex: ou=People,dc=myschool,dc=edu
    • @param s_object_context of user bind-path -
    • ex: ou=People,dc=myschool,dc=edu leads to bind attempt
    • againt cn=username,ou=People,dc=myschool,dc=edu
    • @param s_email_field in user record
    • @param s_givenname_field in user record
    • @param s_surname_field in user record, usually sn
    • @param s_phone_field in user record
  • */
  • public DefaultSpeakerToLDAP( String s_provider_url,
  • String s_id_field,
  • String s_search_context,
  • String s_object_context,
  • String s_email_field,
  • String s_givenname_field,
  • String s_surname_field,
  • String s_phone_field
  • )
  • {

  • os_provider_url = s_provider_url;
  • os_id_field = s_id_field;
  • os_search_context = s_search_context;
  • os_object_context = s_object_context;
  • os_email_field = s_email_field;
  • os_givenname_field = s_givenname_field;
  • os_surname_field = s_surname_field;
  • os_phone_field = s_phone_field;
  • }

    > +

  • /**
    • Default constructor extracts LDAP-server parameters
    • from ConfigurationManager (dspace.cfg):
    • ldap.provider_url, ldap_id_field,
    • ldap_search_contect, ldap_object_context
  • */
  • public DefaultSpeakerToLDAP() {

  • os_provider_url = ConfigurationManager.getProperty("ldap.provider_url");
  • os_id_field = ConfigurationManager.getProperty("ldap.id_field");
  • os_search_context = ConfigurationManager.getProperty("ldap.search_context");
  • os_object_context = ConfigurationManager.getProperty("ldap.object_context");
  • os_email_field = ConfigurationManager.getProperty("ldap.email_field");
  • os_givenname_field = ConfigurationManager.getProperty("ldap.givenname_field");
  • os_surname_field = ConfigurationManager.getProperty("ldap.surname_field");
  • os_phone_field = ConfigurationManager.getProperty("ldap.phone_field");
  • }

    > +

  • /**
    • contact the ldap server and attempt to authenticate
  • */
  • public DataFromLDAP ldapAuthenticate(String s_netid, String
    s_password ) throws NamingException
  • {
  • if (s_password.equals(""))
  • {

  • return null;
  • }

    > + // 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, os_provider_url);
    +
  • // Authenticate
  • env.put(javax.naming.Context.SECURITY_AUTHENTICATION,
    "simple");
  • final String s_ad_key = "WINDOWS_DOMAIN:";
  • if ( os_object_context.toUpperCase ().startsWith( s_ad_key ) )
    {

  • // Active Directory bind
  • String s_principal = os_object_context.substring( s_ad_key.length () ) + "\" + s_netid;
  • olog.debug( "Binding principal to: " + s_principal );
  • env.put(javax.naming.Context.SECURITY_PRINCIPAL, s_principal );
  • }

    else

    {

  • env.put(javax.naming.Context.SECURITY_PRINCIPAL, os_id_field+"="+s_netid+","+os_object_context);
  • }

    > + env.put(javax.naming.Context.SECURITY_CREDENTIALS,
    s_password);
    +

  • DirContext ctx = new InitialDirContext(env);
  • try {
  • Attributes search_attributes = new BasicAttributes(true);
  • search_attributes.put(new BasicAttribute(os_id_field,
    s_netid));
    +
  • String[] v_result_atts = {os_email_field, os_givenname_field, os_surname_field, os_phone_field}

    ;
    +

  • olog.debug( "Searching LDAP for " + os_id_field + "=" +
    s_netid
    • " under " + os_search_context
  • );
    +
  • NamingEnumeration answer = ctx.search(os_search_context,
  • "(" + os_id_field +
    "=" + s_netid + ")",
  • new SearchControls(
    SearchControls.SUBTREE_SCOPE,
    +
    1, 20000,
    +
    v_result_atts,
    +
    false, false
    +
    )
  • );
  • if( ! answer.hasMore()) {

  • olog.info( "Able to bind as " + s_netid + ", but unable to find LDAP record" );
  • return null;
  • }

    > + // look up attributes

  • String ldapEmail = null;
  • String ldapGivenName = null;
  • String ldapSurname = null;
  • String ldapPhone = null;
  • SearchResult sr = (SearchResult)answer.next();
  • Attributes atts = sr.getAttributes();
  • Attribute att;
    +
  • if (v_result_atts[0]!=null) {

  • att = atts.get(v_result_atts[0]);
  • if (att != null) ldapEmail = (String)att.get();
  • }

    > +

  • if (v_result_atts[1]!=null) {
  • att = atts.get(v_result_atts[1]);
  • if (att != null) {

  • ldapGivenName = (String)att.get();
  • }

    > + }
    +

  • if (v_result_atts[2]!=null) {
  • att = atts.get(v_result_atts[2]);
  • if (att != null) {

  • ldapSurname = (String)att.get();
  • }

    > + }
    +

  • if (v_result_atts[3]!=null) {
  • att = atts.get(v_result_atts[3]);
  • if (att != null) {

  • ldapPhone = (String)att.get();
  • }

    > + }

  • return new SimpleDataFromLDAP( ldapEmail, ldapGivenName,
    ldapSurname, ldapPhone, s_netid );
  • } finally {
  • // Close the context when we're done
  • try {
  • if (ctx != null) {

  • ctx.close();
  • }

    > + } catch (NamingException e) { }

  • }
  • // Unreachable
  • }
    +}
    Index: src/main/java/org/dspace/authenticate/SimpleDataFromLDAP.java
    ===================================================================

    src/main/java/org/dspace/authenticate/SimpleDataFromLDAP.java (revision
    0)
    +++
    src/main/java/org/dspace/authenticate/SimpleDataFromLDAP.java (revision
    0)
    @@ -0,0 +1,48 @@
    +package org.dspace.authenticate;
    +
    +
    +/**
    • Simple implementation of DataFromLDAP
  • */
    +public class SimpleDataFromLDAP implements DataFromLDAP {
  • /**
    • Constructor injects all the property values
  • */
  • public SimpleDataFromLDAP ( String s_email,
  • String s_given_name,
  • String s_surname,
  • String s_phone,
  • String s_netid
  • ) {

  • os_email = s_email;
  • os_given_name = s_given_name;
  • os_surname = s_surname;
  • os_phone = s_phone;
  • os_netid = s_netid;
  • }

    > +

  • private final String os_email;
  • public String getEmail () {

  • return os_email;
  • }

    > +

  • private final String os_given_name;
  • public String getGivenName () {

  • return os_given_name;
  • }

    > +

  • private final String os_surname;
  • public String getSurname() {

  • return os_surname;
  • }

    > +

  • private final String os_phone;
  • public String getPhone () {

  • return os_phone;
  • }

    > +

  • private final String os_netid;
  • public String getNetId () {

  • return os_netid;
  • }

    > +}
    Index: src/main/java/org/dspace/authenticate/SpeakerToLDAP.java
    ===================================================================

    src/main/java/org/dspace/authenticate/SpeakerToLDAP.java (revision
    0)
    +++
    src/main/java/org/dspace/authenticate/SpeakerToLDAP.java (revision
    0)
    @@ -0,0 +1,20 @@
    +package org.dspace.authenticate;
    +
    +import javax.naming.NamingException;
    +
    +/**

    • Interface for LDAP interaction handler
  • */
    +public interface SpeakerToLDAP {

  • /**
    • Authenticate the given user with LDAP,
    • and get some data about him/her from the directory.
  • *
    • @param s_netid cn of the user to authenticate
    • @param s_password
    • @return user info
  • */
  • public DataFromLDAP ldapAuthenticate( String s_netid,
  • String s_password
  • ) throws NamingException; +}

    > Index: src/main/java/org/dspace/authenticate/DataFromLDAP.java
    ===================================================================

    src/main/java/org/dspace/authenticate/DataFromLDAP.java (revision
    0)
    +++
    src/main/java/org/dspace/authenticate/DataFromLDAP.java (revision
    0)
    @@ -0,0 +1,15 @@
    +package org.dspace.authenticate;
    +
    +
    +/**

    • POJO data bucket interface for user-data obtained during
    • LDAP authentication
  • */
    +public interface DataFromLDAP {

  • public String getEmail ();
  • public String getGivenName ();
  • public String getSurname();
  • public String getPhone ();
  • /* LDAP common name (cn) for the user /
  • public String getNetId (); +}

    >


dspace-bot commented 14 years ago

vly said:

[14:13] DS-50 - Major/Improvement - LDAP+Active Directory authentication patch - ID: 2100378 - http://jira.dspace.org/jira/browse/DS-50 - [unassigned / Charles Kiplagat]
[14:14] 0
[14:14] -1 (owner can re-open and justify if they want)
[14:14] 0
[14:14] 0
[14:14] 0
[14:14] 0
[14:14] 0
[14:14] 0
[14:14] 0
[14:14] 0
[14:14] One minute: -1 no owner, mark 'won't fix' for DS-50

dspace-bot commented 14 years ago

vly said:

[14:14] LDAPHierarchicalAuth seems to take care of issue.

See also:
http://wiki.dspace.org/index.php/JIRA_Cleanup#2009-08-25

dspace-bot commented 14 years ago

stuartlewis said:

If this is still an issue, please re-open or resubmit. Hopefully this should work now with the hierarchical LDAP solution introduced in 1.5.2.