LP#1786552: LDAP bind user option
authorJeff Davis <jdavis@sitka.bclibraries.ca>
Wed, 17 Oct 2018 01:24:00 +0000 (18:24 -0700)
committerGalen Charlton <gmc@equinoxinitiative.org>
Fri, 6 Sep 2019 21:43:23 +0000 (17:43 -0400)
Signed-off-by: Jeff Davis <jdavis@sitka.bclibraries.ca>
Signed-off-by: Galen Charlton <gmc@equinoxinitiative.org>

Open-ILS/examples/opensrf.xml.example
Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy.pm
Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy/LDAP_Auth.pm

index 156562b..b0ed255 100644 (file)
@@ -544,6 +544,7 @@ vim:et:ts=4:sw=4:
                             <basedn>ou=people,dc=domain,dc=com</basedn>
                             <authid>cn=username,ou=specials,dc=domain,dc=com</authid>
                             <id_attr>uid</id_attr>
+                            <bind_attr>uid</bind_attr>
                             <password>my_ldap_password_for_authid_user</password>
                             <login_types>
                                 <type>staff</type>
index 1f7832c..9b5198e 100644 (file)
@@ -176,6 +176,10 @@ sub login {
     return OpenILS::Event->new( 'LOGIN_FAILED' )
       unless (&enabled() and ($args->{'username'} or $args->{'barcode'}));
 
+    # provided username may not be the user's actual EG username;
+    # hang onto the provided value (if any) so we can use it later
+    $args->{'provided_username'} = $args->{'username'};
+
     if ($args->{barcode} and !$args->{username}) {
         # translate barcode logins into username logins by locating
         # the matching card/user and collecting the username.
@@ -232,10 +236,15 @@ sub login {
         if ($code) {
             push @error_events, $event;
         } elsif (defined $code) { # code is '0', i.e. SUCCESS
-            if (exists $event->{'payload'}) { # we have a complete native login
+            if ($authenticator->name eq 'native' and exists $event->{'payload'}) { # we have a complete native login
                 return $event;
             } else { # create an EG session for the successful external login
-                #
+                # if external login returns a payload, that payload is the
+                # user's Evergreen username
+                if ($event->{'payload'}) {
+                    $args->{'username'} = $event->{'payload'};
+                }
+
                 # before we actually create the session, let's first check if
                 # Evergreen thinks this user is allowed to login
                 #
@@ -251,6 +260,10 @@ sub login {
                     $logger->debug("Authenticated username '" . $args->{'username'} . "' has no Evergreen account, aborting");
                     return OpenILS::Event->new( 'LOGIN_FAILED' );
                 } else {
+                    # TODO: verify that this authenticator is allowed to do auth
+                    # for the specified username (i.e. if the authenticator is for
+                    # Library A only, it shouldn't be able to do auth for
+                    # Library B's users)
                     $args->{user_id} = $user->[0]->id;
                 }
 
index a180e3a..863846a 100644 (file)
@@ -34,15 +34,50 @@ sub authenticate {
     my $authid      = $self->{'authid'};
     my $authid_pass = $self->{'password'};
     $id_attr        = $self->{'id_attr'} || $id_attr;
+    my $bind_attr   = $self->{'bind_attr'} || $id_attr; # use id_attr to bind if bind_attr not set
+
+    # bind_attr: name of LDAP attribute containing user's LDAP username
+    # id_attr: name of LDAP attribute containing user's Evergreen username
+    #
+    # Normally the LDAP username *is* the Evergreen username, in which case
+    # we don't need the extra step of getting the username from the LDAP entry.
+    # Thus, bind_attr and id_attr are the same.  (This is the default scenario.)
+    #
+    # However, suppose we have two college libraries in a consortium.  Each
+    # college has its own LDAP-based SSO solution.  An LDAP username like
+    # "jsmith" may be in use at both libraries, for two different patrons.
+    # In this case, we can't use the LDAP username as the EG username, since
+    # every patron must have a unique username in EG.
+    #
+    # Here's how we handle this second scenario:
+    #
+    # 1. The user logs in with their LDAP username.
+    # 2. EG makes a bind request to the LDAP server using the LDAP username,
+    # which is in the LDAP attribute specified by bind_attr.
+    # 3. If the bind succeeds, we pull the user's EG username from the LDAP
+    # attribute specified by id_attr, and pass it along so that EG looks up the
+    # correct user.
+    #
+    # If bind_attr is not set, or if it specifies the same LDAP attribute as
+    # id_attr, we fallback to the default scenario.
+    #
+    my $username_from_ldap = $bind_attr eq $id_attr ? 0 : 1;
+
+    # When the EG username is retrieved from the LDAP server, we want to ensure
+    # that we bind using the actual username provided by the user.
+    if ($username_from_ldap) {
+        $username = $args->{'provided_username'} || $username;
+    }
 
     my $ldap;
+    my $ldap_search;
     if ( $ldap = Net::LDAP->new($hostname) ) {
         $hostname_is_ldap = 1;
         if ( $ldap->bind( $authid, password => $authid_pass )->code == 0 ) {
             $reached_ldap = 1;
             # verify username and lookup user's DN
-            my $ldap_search = $ldap->search( base => $basedn,
-                                             filter => "($id_attr=$username)" );
+            $ldap_search = $ldap->search( base => $basedn,
+                                             filter => "($bind_attr=$username)" );
             if ( $ldap_search->count != 0 ) {
                 $user_in_ldap = 1;
 
@@ -57,7 +92,12 @@ sub authenticate {
     }
 
     if ( $login_succeeded ) {
-        return OpenILS::Event->new('SUCCESS');
+        if ($username_from_ldap) {
+            my $id_attr_val = $ldap_search->entry(0)->get_value($id_attr);
+            return OpenILS::Event->new('SUCCESS', payload => $id_attr_val);
+        } else {
+            return OpenILS::Event->new('SUCCESS');
+        }
     } elsif ( !$hostname_is_ldap ) {
         # TODO: custom failure events?
         $logger->debug("User login failed: Incorrect LDAP hostname");