-# -*- tab-width: 8 -*-
-# NOTE: This file uses 8-character tabs; do not change the tab size!
-
-package C4::Auth;
+package C4::Auth_with_ldap;
# Copyright 2000-2002 Katipo Communications
#
use strict;
use Digest::MD5 qw(md5_base64);
-require Exporter;
use C4::Context;
-use C4::Output; # to get the template
-use C4::Members;
+use C4::Members qw(AddMember );
use Net::LDAP;
+use Net::LDAP::Filter;
# use Net::LDAP qw(:all);
use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS);
BEGIN {
+ require Exporter;
$VERSION = 3.01; # set the version for version checking
+ our $debug = $ENV{DEBUG} || 0;
@ISA = qw(Exporter C4::Auth);
- @EXPORT = qw(&checkauth &get_template_and_user);
+ @EXPORT = qw( checkauth );
}
=head1 NAME
=head1 SYNOPSIS
- use CGI;
- use C4::Auth;
-
- my $query = new CGI;
-
- my ($template, $borrowernumber, $cookie)
- = get_template_and_user({
- template_name => "opac-main.tmpl",
- query => $query,
- type => "opac",
- authnotrequired => 1,
- flagsrequired => {circulate => 1},
- });
-
- print $query->header(
- -type => 'utf-8',
- -cookie => $cookie
- ), $template->output;
+ use C4::Auth_with_ldap;
=head1 LDAP specific
- This module is specific to LDAP authentification. It requires Net::LDAP package and a working LDAP server.
+ This module is specific to LDAP authentification. It requires Net::LDAP package and one or more
+ working LDAP servers.
To use it :
- * move initial Auth.pm elsewhere
- * Search the string LOCAL
- * modify the code between LOCAL and /LOCAL to fit your LDAP server parameters & fields
- * rename this module to Auth.pm
- That should be enough.
+ * modify the code between LOCAL and /LOCAL to fit your LDAP server parameters & fields.
-=head1 FUNCTIONS
+ 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.
=cut
-# Redefine checkpw
-# connects to LDAP (anonymous)
-# retrieves $userid a-login
-# then compares $password with a-weak
-# then gets the LDAP entry
-# and calls the memberadd if necessary
+# Redefine checkauth:
+# connect to LDAP (named or anonymous)
+# ~ retrieves $userid from "uid"
+# ~ then compares $password with userPassword
+# ~ then gets the LDAP entry
+# ~ and calls the memberadd if necessary
my %mapping = (
firstname => 'givenName',
phone => 'telephoneNumber',
);
-sub checkpw {
+my (@ldaphosts) = (qw(localhost)); # potentially multiple LDAP hosts!
+my $base = "dc=metavore,dc=com";
+my $ldapname = "cn=Manager,$base"; # The LDAP user.
+my $ldappassword = 'metavore';
+
+my %config = (
+ anonymous => ($ldapname and $ldappassword) ? 0 : 1,
+ replicate => 0, # add from LDAP to Koha database for new user
+ update => 0, # update from LDAP to Koha database for existing user
+);
+
+sub description ($) {
+ my $result = shift or return undef;
+ return "LDAP error #" . $result->code
+ . ": " . $result->error_name . "\n"
+ . "# " . $result->error_text . "\n";
+}
+
+sub checkauth {
my ($dbh, $userid, $password) = @_;
if ( $userid eq C4::Context->config('user')
&& $password eq C4::Context->config('pass') )
{
return 2; # Koha superuser account
}
- ##################################################
- ### LOCAL
- ### Change the code below to match your own LDAP server.
- ##################################################
- # LDAP connexion parameters
- my $ldapserver = 'localhost';
-
- # Infos to do an anonymous bind
- my $name = "dc=metavore,dc=com";
- my $db = Net::LDAP->new($ldapserver);
-
- # do an anonymous bind
- my $res = $db->bind();
- if ($res->code) { # auth refused
- warn "LDAP Auth impossible : server not responding";
+ my $db = Net::LDAP->new(\@ldaphosts);
+ #$debug and $db->debug(5);
+ my $filter = Net::LDAP::Filter->new("uid=$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);
return 0;
}
- my $userdnsearch = $db->search(
- base => $name,
- filter => "(a-login=$userid)",
- );
- if ( $userdnsearch->code || !( $userdnsearch->count eq 1 ) ) {
- warn "LDAP Auth rejected : user unknown in LDAP";
+ my $search = $db->search(
+ base => $base,
+ filter => $filter,
+ # attrs => ['*'],
+ ) or die "LDAP search failed to return object.";
+ my $count = $search->count;
+ if ($search->code > 0) {
+ warn sprintf("LDAP Auth rejected : %s gets %d hits\n", $filter->as_string, $count) . description($search);
+ return 0;
+ }
+ if ($count != 1) {
+ warn sprintf("LDAP Auth rejected : %s gets %d hits\n", $filter->as_string, $count);
return 0;
}
- my $userldapentry = $userdnsearch->shift_entry;
- my $cmpmesg = $db->compare( $userldapentry, attr => 'a-weak', value => $password );
+ my $userldapentry = $search->shift_entry;
+ my $cmpmesg = $db->compare( $userldapentry, attr=>'userPassword', value => $password );
if($cmpmesg->code != 6) {
- warn "LDAP Auth rejected : wrong password";
+ 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);
+ if (exists_local($userid)) {
+ ($config{update} ) and &update_local($userid,$password,%borrower);
+ } else {
+ ($config{replicate}) and AddMember(%borrower);
+ }
+ return 1;
+}
- # build LDAP hash
+# Pass LDAP entry object and local cardnumber (userid).
+# Returns borrower hash.
+# Edit %mapping so $memberhash{'xxx'} fits your ldap structure.
+# Ensure that mandatory fields are correctly filled!
+#
+sub ldap_entry_2_hash ($$) {
+ my $userldapentry = shift;
+ my %borrower = ( cardnumber => shift );
my %memberhash;
- my $x = $userldapentry->{asn}{attributes};
+ my $x = $userldapentry->{asn}{attributes} or return undef;
my $key;
foreach my $k (@$x) {
foreach my $k2 ( keys %$k ) {
}
}
}
-
- # BUILD %borrower to CREATE or MODIFY BORROWER
- # change $memberhash{'xxx'} to fit your ldap structure.
- # check twice that mandatory fields are correctly filled
- #
- my %borrower;
- $borrower{cardnumber} = $userid;
foreach my $key (%mapping) {
my $data = $memberhash{$mapping{$key}};
defined $data or $data = ' ';
$borrower{$key} = ($data ne '') ? $data : ' ' ;
}
- $borrower{initials} =
- substr( $borrower{firstname}, 0, 1 )
- . substr( $borrower{surname}, 0, 1 )
- . " "; # MANDATORY FIELD
-##################################################
-### /LOCAL
-##################################################
-# check if borrower exists (then modify, else add)
- my $sth =
- $dbh->prepare("select password from borrowers where cardnumber=?");
- $sth->execute($userid);
- if ( $sth->rows ) {
- # warn "MODIFY borrower";
- my $sth2 = $dbh->prepare("
-UPDATE borrowers set firstname=?,surname=?,initials=?,streetaddress=?,city=?,phone=?, categorycode=?,branchcode=?,emailaddress=?,sort1=?
-WHERE cardnumber=?
- ");
- $sth2->execute(
- $borrower{firstname}, $borrower{surname},
- $borrower{initials}, $borrower{streetaddress},
- $borrower{city}, $borrower{phone},
- $borrower{categorycode}, $borrower{branchcode},
- $borrower{emailaddress}, $borrower{sort1},
- $userid
- );
- } else {
- # warn "ADD borrower";
- my $borrowerid = newmember(%borrower);
- }
+ $borrower{initials} = $memberhash{initials} ||
+ ( substr($borrower{'firstname'},0,1)
+ . substr($borrower{ 'surname' },0,1)
+ . " ");
+ return %borrower;
+}
- # CREATE or MODIFY PASSWORD/LOGIN
+sub exists_local($) {
+ my $sth = C4::Context->dbh->prepare("SELECT password from borrowers WHERE cardnumber=?");
+ $sth->execute(shift);
+ return ($sth->rows) ? 1 : 0 ;
+}
+
+sub update_local($$%) {
+ # warn "MODIFY borrower";
+ my $userid = shift or return undef;
+ my $digest = md5_base64(shift) or return undef;
+ my %borrower = shift or return undef;
+ my $dbh = C4::Context->dbh;
+ my $sth = $dbh->prepare("
+UPDATE borrowers
+SET firstname=?,surname=?,initials=?,streetaddress=?,city=?,phone=?, categorycode=?,branchcode=?,emailaddress=?,sort1=?
+WHERE cardnumber=?
+ ");
+ $sth->execute(
+ $borrower{firstname}, $borrower{surname},
+ $borrower{initials}, $borrower{streetaddress},
+ $borrower{city}, $borrower{phone},
+ $borrower{categorycode}, $borrower{branchcode},
+ $borrower{emailaddress}, $borrower{sort1},
+ $userid
+ );
+
+ # MODIFY PASSWORD/LOGIN
# search borrowerid
- $sth = $dbh->prepare("SELECT borrowernumber from borrowers WHERE cardnumber=?");
+ $sth = $dbh->prepare("SELECT borrowernumber from borrowers WHERE cardnumber=? ");
$sth->execute($userid);
my ($borrowerid) = $sth->fetchrow;
+ # warn "change local password for $borrowerid setting $password";
+ changepassword($userid, $borrowerid, $digest);
+
+ # Confirm changes
+ my $cardnumber;
+ $sth = $dbh->prepare("SELECT password,cardnumber from borrowers WHERE userid=? ");
+ $cardnumber = confirmer($sth,$userid,$digest) and return $cardnumber;
+ $sth = $dbh->prepare("SELECT password,cardnumber from borrowers WHERE cardnumber=? ");
+ $cardnumber = confirmer($sth,$userid,$digest) and return $cardnumber;
+ die "Unexpected error after password update to $userid / $cardnumber.";
+}
- # warn "change password for $borrowerid setting $password";
- my $digest = md5_base64($password);
- changepassword( $userid, $borrowerid, $digest );
-
- # INTERNAL AUTH
- $sth = $dbh->prepare("SELECT password,cardnumber from borrowers WHERE userid=?");
+sub confirmer($$) {
+ my $sth = shift or return undef;
+ my $userid = shift or return undef;
+ my $digest = shift or return undef;
$sth->execute($userid);
- if ( $sth->rows ) {
- my ( $md5password, $cardnumber ) = $sth->fetchrow;
- if ( md5_base64($password) eq $md5password ) {
- return 1, $cardnumber;
- }
- }
- $sth = $dbh->prepare("SELECT password from borrowers WHERE cardnumber=?");
- $sth->execute($userid);
- if ($sth->rows) {
- my ($md5password) = $sth->fetchrow;
- if ( md5_base64($password) eq $md5password ) {
- return 1, $userid;
- }
+ if ($sth->rows) {
+ my ($md5password, $othernum) = $sth->fetchrow;
+ ($digest eq $md5password) and return $othernum;
+ warn "Password mismatch after update to userid=$userid";
+ return undef;
}
- return 0;
+ warn "Could not recover record after updating password for userid=$userid";
+ return 0;
}
-
-END { } # module clean-up code here (global destructor)
1;
__END__
CGI(3)
-C4::Output(3)
+Net::LDAP()
Digest::MD5(3)