#!/usr/bin/perl # This file is part of Koha. # # Koha is free software; you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # Koha is distributed in the hope that it will be useful, but # WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Koha; if not, see . use Modern::Perl; use Test::More tests => 4; use Test::MockModule; use Test::MockObject; use Test::Warn; use C4::Context; my $dbh = C4::Context->dbh; # Start transaction $dbh->{ AutoCommit } = 0; $dbh->{ RaiseError } = 1; # Variables controlling LDAP server config my $update = 0; my $replicate = 0; my $auth_by_bind = 1; my $anonymous_bind = 1; # Variables controlling LDAP behaviour my $desired_authentication_result = 'success'; my $desired_connection_result = 'error'; my $desired_bind_result = 'error'; my $desired_compare_result = 'error'; my $desired_search_result = 'error'; my $desired_count_result = 1; my $non_anonymous_bind_result = 'error'; my $ret; # Mock the context module my $context = new Test::MockModule( 'C4::Context' ); $context->mock( 'config', \&mockedC4Config ); # Mock the Net::LDAP module my $ldap = new Test::MockModule( 'Net::LDAP' ); $ldap->mock( 'new', sub { if ( $desired_connection_result eq 'error' ) { # We were asked to fail the LDAP conexion return; } else { # Return a mocked Net::LDAP object (Test::MockObject) return mock_net_ldap(); } }); # C4::Auth_with_ldap needs several stuff set first ^^^ use_ok( 'C4::Auth_with_ldap' ); can_ok( 'C4::Auth_with_ldap', qw/ checkpw_ldap search_method /); subtest "checkpw_ldap tests" => sub { plan tests => 4; ## Connection fail tests $desired_connection_result = 'error'; warning_is { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } "LDAP connexion failed", "checkpw_ldap prints correct warning if LDAP conexion fails"; is( $ret, 0, "checkpw_ldap returns 0 if LDAP conexion fails"); ## Connection success tests $desired_connection_result = 'success'; subtest "auth_by_bind = 1 tests" => sub { plan tests => 5; $auth_by_bind = 1; $desired_authentication_result = 'success'; $anonymous_bind = 1; $desired_bind_result = 'error'; $desired_search_result = 'error'; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } qr/Anonymous LDAP bind failed: LDAP error #1: error_name/, "checkpw_ldap prints correct warning if LDAP anonymous bind fails"; is( $ret, 0, "checkpw_ldap returns 0 if LDAP anonymous bind fails"); $desired_authentication_result = 'success'; $anonymous_bind = 1; $desired_bind_result = 'success'; $desired_search_result = 'success'; $desired_count_result = 0; # user auth problem $non_anonymous_bind_result = 'success'; reload_ldap_module(); is ( C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ), 0, "checkpw_ldap returns 0 if user lookup returns 0"); $non_anonymous_bind_result = 'error'; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/, "checkpw_ldap prints correct warning if LDAP bind fails"; is ( $ret, -1, "checkpw_ldap returns -1 LDAP bind fails for user (Bug 8148)"); }; subtest "auth_by_bind = 0 tests" => sub { plan tests => 8; $auth_by_bind = 0; # Anonymous bind $anonymous_bind = 1; $desired_bind_result = 'error'; $non_anonymous_bind_result = 'error'; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/, "checkpw_ldap prints correct warning if LDAP bind fails"; is ( $ret, 0, "checkpw_ldap returns 0 if bind fails"); $anonymous_bind = 1; $desired_bind_result = 'success'; $non_anonymous_bind_result = 'success'; $desired_compare_result = 'error'; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/, "checkpw_ldap prints correct warning if LDAP bind fails"; is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)"); # Non-anonymous bind $anonymous_bind = 0; $desired_bind_result = 'success'; $non_anonymous_bind_result = 'error'; $desired_compare_result = 'dont care'; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/, "checkpw_ldap prints correct warning if LDAP bind fails"; is ( $ret, 0, "checkpw_ldap returns 0 if bind fails"); $anonymous_bind = 0; $desired_bind_result = 'success'; $non_anonymous_bind_result = 'success'; $desired_compare_result = 'error'; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/, "checkpw_ldap prints correct warning if LDAP bind fails"; is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)"); }; }; subtest "search_method tests" => sub { plan tests => 5; my $ldap = mock_net_ldap(); # Null params tests is( C4::Auth_with_ldap::search_method( $ldap, undef), undef, "search_method returns undef on undefined userid"); is( C4::Auth_with_ldap::search_method( undef, "undef"), undef, "search_method returns undef on undefined ldap object"); # search ->code and !->code $desired_search_result = 'error'; reload_ldap_module(); eval { $ret = C4::Auth_with_ldap::search_method( $ldap, "undef"); }; like( $@, qr/LDAP search failed to return object : 1/, "search_method prints correct warning when db->search returns error code"); $desired_search_result = 'success'; $desired_count_result = 2; reload_ldap_module(); warning_like { $ret = C4::Auth_with_ldap::search_method( $ldap, '123') } qr/^LDAP Auth rejected \: \(uid\=123\) gets 2 hits/, "search_method prints correct warning when hits count is not 1"; is( $ret, 0, "search_method returns 0 when hits count is not 1" ); }; # Function that mocks the call to C4::Context->config(param) sub mockedC4Config { my $param = shift; my %ldap_mapping = ( firstname => { is => 'givenname' }, surname => { is => 'sn' }, address => { is => 'postaladdress' }, city => { is => 'l' }, zipcode => { is => 'postalcode' }, branchcode => { is => 'branch' }, userid => { is => 'uid' }, password => { is => 'userpassword' }, email => { is => 'mail' }, categorycode => { is => 'employeetype' }, phone => { is => 'telephonenumber' } ); my %ldap_config = ( anonymous_bind => $anonymous_bind, auth_by_bind => $auth_by_bind, base => 'dc=metavore,dc=com', hostname => 'localhost', mapping => \%ldap_mapping, pass => 'metavore', principal_name => '%s@my_domain.com', replicate => $replicate, update => $update, user => 'cn=Manager,dc=metavore,dc=com' ); return \%ldap_config; }; # Function that mocks the call to Net::LDAP sub mock_net_ldap { my $mocked_ldap = Test::MockObject->new(); $mocked_ldap->mock( 'bind', sub { my @args = @_; my $mocked_message; if ( $#args > 1 ) { # Args passed => non-anonymous bind if ( $non_anonymous_bind_result eq 'error' ) { return mock_net_ldap_message(1,1,'error_name','error_text'); } else { return mock_net_ldap_message(0,0,'',''); } } else { $mocked_message = mock_net_ldap_message( ($desired_bind_result eq 'error' ) ? 1 : 0, # code ($desired_bind_result eq 'error' ) ? 1 : 0, # error ($desired_bind_result eq 'error' ) ? 'error_name' : 0, # error_name ($desired_bind_result eq 'error' ) ? 'error_text' : 0 # error_text ); } return $mocked_message; }); $mocked_ldap->mock( 'compare', sub { my $mocked_message; if ( $desired_compare_result eq 'error' ) { $mocked_message = mock_net_ldap_message(1,1,'error_name','error_text'); } else { # we expect return code 6 for success $mocked_message = mock_net_ldap_message(6,0,'',''); } return $mocked_message; }); $mocked_ldap->mock( 'search', sub { return mock_net_ldap_search( ( $desired_count_result ) # count ? $desired_count_result : 1, # default to 1 ( $desired_search_result eq 'error' ) # code ? 1 : 0, # 0 == success ( $desired_search_result eq 'error' ) # error ? 1 : 0, ( $desired_search_result eq 'error' ) # error_text ? 'error_text' : undef, ( $desired_search_result eq 'error' ) # error_name ? 'error_name' : undef, mock_net_ldap_entry( 'sampledn', 1 ) # shift_entry ); }); return $mocked_ldap; } sub mock_net_ldap_search { my ( $count, $code, $error, $error_text, $error_name, $shift_entry ) = @_; my $mocked_search = Test::MockObject->new(); $mocked_search->mock( 'count', sub { return $count; } ); $mocked_search->mock( 'code', sub { return $code; } ); $mocked_search->mock( 'error', sub { return $error; } ); $mocked_search->mock( 'error_name', sub { return $error_name; } ); $mocked_search->mock( 'error_text', sub { return $error_text; } ); $mocked_search->mock( 'shift_entry', sub { return $shift_entry; } ); return $mocked_search; } sub mock_net_ldap_message { my ( $code, $error, $error_name, $error_text ) = @_; my $mocked_message = Test::MockObject->new(); $mocked_message->mock( 'code', sub { $code } ); $mocked_message->mock( 'error', sub { $error } ); $mocked_message->mock( 'error_name', sub { $error_name } ); $mocked_message->mock( 'error_text', sub { $error_text } ); return $mocked_message; } sub mock_net_ldap_entry { my ( $dn, $exists ) = @_; my $mocked_entry = Test::MockObject->new(); $mocked_entry->mock( 'dn', sub { return $dn; } ); $mocked_entry->mock( 'exists', sub { return $exists } ); return $mocked_entry; } # TODO: Once we remove the global variables in C4::Auth_with_ldap # we shouldn't need this... # ... Horrible hack sub reload_ldap_module { delete $INC{'C4/Auth_with_ldap.pm'}; require C4::Auth_with_ldap; C4::Auth_with_ldap->import; } $dbh->rollback; 1;