removing comments
[koha.git] / C4 / Auth_with_ldap.pm
index 2798da3..755cfba 100644 (file)
@@ -20,6 +20,7 @@ package C4::Auth_with_ldap;
 use strict;
 use Digest::MD5 qw(md5_base64);
 
+use C4::Debug;
 use C4::Context;
 use C4::Members qw(AddMember changepassword);
 use C4::Utils qw( :all );
@@ -30,15 +31,14 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug);
 
 BEGIN {
        require Exporter;
-       $VERSION = 3.01;        # set the version for version checking
-       $debug = $ENV{DEBUG} || 0;
-       @ISA    = qw(Exporter C4::Auth);
-       @EXPORT = qw( checkpw );
+       $VERSION = 3.03;        # set the version for version checking
+       @ISA    = qw(Exporter);
+       @EXPORT = qw( checkpw_ldap );
 }
 
-# Redefine checkpw:
+# Redefine checkpw_ldap:
 # connect to LDAP (named or anonymous)
-# ~ retrieves $userid from "uid"
+# ~ retrieves $userid from KOHA_CONF mapping
 # ~ then compares $password with userPassword 
 # ~ then gets the LDAP entry
 # ~ and calls the memberadd if necessary
@@ -49,11 +49,11 @@ sub ldapserver_error ($) {
 
 use vars qw($mapping @ldaphosts $base $ldapname $ldappassword);
 my $context = C4::Context->new()       or die 'C4::Context->new failed';
-my $ldap = $context->{server}->{ldapserver}    or die 'No "ldapserver" in server hash from KOHA_CONF: ' . $ENV{KOHA_CONF};
+my $ldap = C4::Context->config("ldapserver") or die 'No "ldapserver" in server hash from KOHA_CONF: ' . $ENV{KOHA_CONF};
 my $prefhost  = $ldap->{hostname}      or die ldapserver_error('hostname');
 my $base      = $ldap->{base}          or die ldapserver_error('base');
-$ldapname     = $ldap->{user}          or die ldapserver_error('user');
-$ldappassword = $ldap->{pass}          or die ldapserver_error('pass');
+$ldapname     = $ldap->{user}          ;
+$ldappassword = $ldap->{pass}          ;
 our %mapping  = %{$ldap->{mapping}}    or die ldapserver_error('mapping');
 my @mapkeys = keys %mapping;
 $debug and print STDERR "Got ", scalar(@mapkeys), " ldap mapkeys (  total  ): ", join ' ', @mapkeys, "\n";
@@ -73,16 +73,12 @@ sub description ($) {
                        . "# " . $result->error_text . "\n";
 }
 
-sub checkpw {
+sub checkpw_ldap {
     my ($dbh, $userid, $password) = @_;
-    if (   $userid   eq C4::Context->config('user')
-        && $password eq C4::Context->config('pass') )
-    {
-        return 2;      # Koha superuser account
-    }
     my $db = Net::LDAP->new([$prefhost]);
        #$debug and $db->debug(5);
-       my $filter = Net::LDAP::Filter->new("uid=$userid") or die "Failed to create new Net::LDAP::Filter";
+       my $uid_field = $mapping{userid}->{is} or die ldapserver_error("mapping for 'userid'");
+       my $filter = Net::LDAP::Filter->new("$uid_field=$userid") or die "Failed to create new Net::LDAP::Filter";
     my $res = ($config{anonymous}) ? $db->bind : $db->bind($ldapname, password=>$ldappassword);
     if ($res->code) {          # connection refused
         warn "LDAP bind failed as $ldapname: " . description($res);
@@ -104,17 +100,28 @@ sub checkpw {
        }
 
        my $userldapentry = $search->shift_entry;
-       my $cmpmesg = $db->compare( $userldapentry, attr=>'userpassword', value => $password );
-       if ($cmpmesg->code != 6) {
-               warn "LDAP Auth rejected : invalid password for user '$userid'. " . description($cmpmesg);
-               return 0;
+       if ( $ldap->{auth_by_bind} ) {
+               my $user_ldapname = $userldapentry->dn();
+               my $user_db = Net::LDAP->new( [$prefhost] );
+               $res = $user_db->bind( $user_ldapname, password => $password );
+               if ( $res->code ) {
+                       $debug and warn "Bind as user failed ". description( $res );
+                       return 0;
+               }
+       } else {
+               my $cmpmesg = $db->compare( $userldapentry, attr=>'userpassword', value => $password );
+               if ($cmpmesg->code != 6) {
+                       warn "LDAP Auth rejected : invalid password for user '$userid'. " . description($cmpmesg);
+                       return 0;
+               }
        }
        unless ($config{update} or $config{replicate}) {
                return 1;
        }
        my %borrower = ldap_entry_2_hash($userldapentry,$userid);
-       $debug and print "checkpw received \%borrower w/ " . keys(%borrower), " keys: ", join(' ', keys %borrower), "\n";
-       my ($borrowernumber,$cardnumber,$userid,$savedpw) = exists_local($userid);
+       $debug and print "checkpw_ldap received \%borrower w/ " . keys(%borrower), " keys: ", join(' ', keys %borrower), "\n";
+       my ($borrowernumber,$cardnumber,$savedpw);
+       ($borrowernumber,$cardnumber,$userid,$savedpw) = exists_local($userid);
        if ($borrowernumber) {
                ($config{update}   ) and my $c2 = &update_local($userid,$password,$borrowernumber,\%borrower) || '';
                ($cardnumber eq $c2) or warn "update_local returned cardnumber '$c2' instead of '$cardnumber'";
@@ -133,8 +140,9 @@ sub ldap_entry_2_hash ($$) {
        my $userldapentry = shift;
        my %borrower = ( cardnumber => shift );
        my %memberhash;
+       $userldapentry->exists('uid');  # This is bad, but required!  By side-effect, this initializes the attrs hash. 
        if ($debug) {
-               print "keys(\%\$userldapentry) = " . join(', ', keys %$userldapentry), "\n", $userldapentry->dump();
+               print "\nkeys(\%\$userldapentry) = " . join(', ', keys %$userldapentry), "\n", $userldapentry->dump();
                foreach (keys %$userldapentry) {
                        print "\n\nLDAP key: $_\t", sprintf('(%s)', ref $userldapentry->{$_}), "\n";
                        hashdump("LDAP key: ",$userldapentry->{$_});
@@ -144,13 +152,13 @@ sub ldap_entry_2_hash ($$) {
        my $key;
        foreach (keys %$x) {
                $memberhash{$_} = join ' ', @{$x->{$_}};        
-               $debug and print sprintf("building \$memberhash{%s} = ", $_), join ' ', @{$x->{$_}}, "\n";
+               $debug and print sprintf("building \$memberhash{%s} = ", $_, join(' ', @{$x->{$_}})), "\n";
        }
        $debug and print "Finsihed \%memberhash has ", scalar(keys %memberhash), " keys\n",
                                        "Referencing \%mapping with ", scalar(keys %mapping), " keys\n";
        foreach my $key (keys %mapping) {
                my  $data = $memberhash{$mapping{$key}->{is}}; 
-               $debug and printf "mapping %20s ==> %-20s ($data)\n", $key, $mapping{$key}->{is};
+               $debug and printf "mapping %20s ==> %-20s (%s)\n", $key, $mapping{$key}->{is}, $data;
                unless (defined $data) { 
                        $data = $mapping{$key}->{content} || '';        # default or failsafe ''
                }
@@ -166,7 +174,7 @@ sub ldap_entry_2_hash ($$) {
 sub exists_local($) {
        my $arg = shift;
        my $dbh = C4::Context->dbh;
-       my $select = "SELECT borrowernumber,cardnumber,userid,password from borrowers ";
+       my $select = "SELECT borrowernumber,cardnumber,userid,password FROM borrowers ";
 
        my $sth = $dbh->prepare("$select WHERE userid=?");      # was cardnumber=?
        $sth->execute($arg);
@@ -185,19 +193,19 @@ sub update_local($$$$) {
        my   $digest   = md5_base64(shift) or return undef;
        my $borrowerid = shift             or return undef;
        my $borrower   = shift             or return undef;
+       my @keys = keys %$borrower;
        my $dbh = C4::Context->dbh;
        my $query = "UPDATE  borrowers\nSET     " . 
-               join(',', map {"$_=?"} keys %$borrower) .                               # don't need to sort: keys order is deterministic
+               join(',', map {"$_=?"} @keys) .
                "\nWHERE   borrowernumber=? "; 
        my $sth = $dbh->prepare($query);
        if ($debug) {
                print STDERR $query, "\n",
-                       join "\n", map {"$_ = " . $borrower->{$_}} 
-                       keys %$borrower;
+                       join "\n", map {"$_ = '" . $borrower->{$_} . "'"} @keys;
                print STDERR "\nuserid = $userid\n";
        }
        $sth->execute(
-               (map {$borrower->{$_}} keys %$borrower), $borrowerid            # relies on deterministic keys order to match above
+               ((map {$borrower->{$_}} @keys), $borrowerid)
        );
 
        # MODIFY PASSWORD/LOGIN
@@ -236,10 +244,10 @@ C4::Auth - Authenticates Koha users
           * Modify ldapserver element in KOHA_CONF
           * Establish field mapping in <mapping> element.
 
-       It is assumed your user records are stored according to the inetOrgPerson schema, RFC#2798.
-       Thus the username must match the "uid" field, and the password must match the "userpassword" field.
+       For example, if your user records are stored according to the inetOrgPerson schema, RFC#2798,
+       the username would match the "uid" field, and the password should match the "userpassword" field.
 
-       Make sure that the required fields are populated in your LDAP database (and mapped in KOHA_CONF).  
+       Make sure that ALL required fields are populated by your LDAP database (and mapped in KOHA_CONF).  
        What are the required fields?  Well, in mysql you can check the database table "borrowers" like this:
 
        mysql> show COLUMNS from borrowers;
@@ -305,32 +313,38 @@ C4::Auth - Authenticates Koha users
 
 =head1 KOHA_CONF and field mapping
 
-Example XML stanza for LDAP conifugration in KOHA_CONF:
-
-       <!-- LDAP SERVER (optional) -->
-       <server id="ldapserver"  listenref="ldapserver">
-               <hostname>localhost</hostname>
-               <base>dc=metavore,dc=com</base>
-               <user>cn=Manager,dc=metavore,dc=com</user>             <!-- DN, if not anonymous -->
-               <pass>metavore</pass>      <!-- password, if not anonymous -->
-               <replicate>1</replicate>   <!-- add new users from LDAP to Koha database -->
-               <update>1</update>         <!-- update existing users in Koha database -->
-               <mapping>                  <!-- match koha SQL field names to your LDAP record field names -->
-               <firstname    is="givenname"      ></firstname>
-               <surname      is="sn"             ></surname>
-               <address      is="postaladdress"  ></address>
-               <city         is="l"              >Athens, OH</city>
-               <zipcode      is="postalcode"     ></zipcode>
-               <branchcode   is="branch"         >MAIN</branchcode>
-               <userid       is="uid"            ></userid>
-               <password     is="userpassword"   ></password>
-               <email        is="mail"           ></email>
-               <categorycode is="employeetype"   >PT</categorycode>
-               <phone        is="telephonenumber"></phone>
-               </mapping>
-       </server>
-
-The <mapping> subelements establishe the relationship between mysql fields and LDAP attributes. The element name
+Example XML stanza for LDAP configuration in KOHA_CONF.
+
+ <config>
+  ...
+  <useldapserver>1</useldapserver>
+  <!-- LDAP SERVER (optional) -->
+  <ldapserver id="ldapserver">
+    <hostname>localhost</hostname>
+    <base>dc=metavore,dc=com</base>
+    <user>cn=Manager,dc=metavore,dc=com</user>             <!-- DN, if not anonymous -->
+    <pass>metavore</pass>          <!-- password, if not anonymous -->
+    <replicate>1</replicate>       <!-- add new users from LDAP to Koha database -->
+    <update>1</update>             <!-- update existing users in Koha database -->
+    <auth_by_bind>0</auth_by_bind> <!-- set to 1 to authenticate by binding instead of
+                                        password comparison, e.g., to use Active Directory -->
+    <mapping>                  <!-- match koha SQL field names to your LDAP record field names -->
+      <firstname    is="givenname"      ></firstname>
+      <surname      is="sn"             ></surname>
+      <address      is="postaladdress"  ></address>
+      <city         is="l"              >Athens, OH</city>
+      <zipcode      is="postalcode"     ></zipcode>
+      <branchcode   is="branch"         >MAIN</branchcode>
+      <userid       is="uid"            ></userid>
+      <password     is="userpassword"   ></password>
+      <email        is="mail"           ></email>
+      <categorycode is="employeetype"   >PT</categorycode>
+      <phone        is="telephonenumber"></phone>
+    </mapping> 
+  </ldapserver> 
+ </config>
+
+The <mapping> subelements establish the relationship between mysql fields and LDAP attributes. The element name
 is the column in mysql, with the "is" characteristic set to the LDAP attribute name.  Optionally, any content
 between the element tags is taken as the default value.  In this example, the default categorycode is "PT" (for
 patron).