From 3a7ca4a2c6c3d6ea893d13a38179e9b4a26e523a Mon Sep 17 00:00:00 2001 From: Jeff Davis Date: Tue, 16 Oct 2018 18:24:00 -0700 Subject: [PATCH] LP#1786552: LDAP bind user option Signed-off-by: Jeff Davis Signed-off-by: Galen Charlton --- Open-ILS/examples/opensrf.xml.example | 1 + .../perlmods/lib/OpenILS/Application/AuthProxy.pm | 17 +++++++- .../lib/OpenILS/Application/AuthProxy/LDAP_Auth.pm | 46 ++++++++++++++++++++-- 3 files changed, 59 insertions(+), 5 deletions(-) diff --git a/Open-ILS/examples/opensrf.xml.example b/Open-ILS/examples/opensrf.xml.example index 156562b82b..b0ed2553be 100644 --- a/Open-ILS/examples/opensrf.xml.example +++ b/Open-ILS/examples/opensrf.xml.example @@ -544,6 +544,7 @@ vim:et:ts=4:sw=4: ou=people,dc=domain,dc=com cn=username,ou=specials,dc=domain,dc=com uid + uid my_ldap_password_for_authid_user staff diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy.pm index 1f7832c653..9b5198e4c7 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy.pm @@ -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; } diff --git a/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy/LDAP_Auth.pm b/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy/LDAP_Auth.pm index a180e3a477..863846a70a 100644 --- a/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy/LDAP_Auth.pm +++ b/Open-ILS/src/perlmods/lib/OpenILS/Application/AuthProxy/LDAP_Auth.pm @@ -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"); -- 2.11.0