View | Details | Raw Unified | Return to bug 8148
Collapse All | Expand All

(-)a/t/db_dependent/Auth_with_ldap.t (-1 / +364 lines)
Line 0 Link Here
0
- 
1
#!/usr/bin/perl
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use Test::More tests => 4;
21
use Test::MockModule;
22
use Test::MockObject;
23
use Test::Warn;
24
25
use C4::Context;
26
use Data::Printer;
27
28
my $dbh = C4::Context->dbh;
29
# Start transaction
30
$dbh->{ AutoCommit } = 0;
31
$dbh->{ RaiseError } = 1;
32
33
# Variables controlling LDAP server config
34
my $update         = 0;
35
my $replicate      = 0;
36
my $auth_by_bind   = 1;
37
my $anonymous_bind = 1;
38
# Variables controlling LDAP behaviour
39
my $desired_authentication_result = 'success';
40
my $desired_connection_result     = 'error';
41
my $desired_bind_result           = 'error';
42
my $desired_compare_result        = 'error';
43
my $desired_search_result         = 'error';
44
my $desired_count_result          = 1;
45
my $non_anonymous_bind_result     = 'error';
46
my $ret;
47
48
# Mock the context module
49
my $context     = new Test::MockModule( 'C4::Context' );
50
$context->mock( 'config', \&mockedC4Config );
51
52
# Mock the Net::LDAP module
53
my $ldap = new Test::MockModule( 'Net::LDAP' );
54
55
$ldap->mock( 'new',  sub {
56
    if ( $desired_connection_result eq 'error' ) {
57
        # We were asked to fail the LDAP conexion
58
        return;
59
    } else {
60
        # Return a mocked Net::LDAP object (Test::MockObject)
61
        return mock_net_ldap();
62
    }
63
});
64
65
66
# C4::Auth_with_ldap needs several stuff set first ^^^
67
use_ok( 'C4::Auth_with_ldap' );
68
can_ok( 'C4::Auth_with_ldap', qw/
69
        checkpw_ldap
70
        search_method /);
71
72
subtest "checkpw_ldap tests" => sub {
73
74
    plan tests => 4;
75
76
    ## Connection fail tests
77
    $desired_connection_result = 'error';
78
    warning_is { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) }
79
        "LDAP connexion failed",
80
        "checkpw_ldap prints correct warning if LDAP conexion fails";
81
    is( $ret, 0, "checkpw_ldap returns 0 if LDAP conexion fails");
82
83
    ## Connection success tests
84
    $desired_connection_result = 'success';
85
86
    subtest "auth_by_bind = 1 tests" => sub {
87
88
        plan tests => 5;
89
90
        $auth_by_bind          = 1;
91
92
        $desired_authentication_result = 'success';
93
        $anonymous_bind        = 1;
94
        $desired_bind_result   = 'error';
95
        $desired_search_result = 'error';
96
        reload_ldap_module();
97
98
99
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
100
                               $dbh, 'hola', password => 'hey' ) }
101
                    qr/Anonymous LDAP bind failed: LDAP error #1: error_name/,
102
                    "checkpw_ldap prints correct warning if LDAP anonymous bind fails";
103
        is( $ret, 0, "checkpw_ldap returns 0 if LDAP anonymous bind fails");
104
105
        $desired_authentication_result = 'success';
106
        $anonymous_bind        = 1;
107
        $desired_bind_result   = 'success';
108
        $desired_search_result = 'success';
109
        $desired_count_result  = 0; # user auth problem
110
        $non_anonymous_bind_result = 'success';
111
        reload_ldap_module();
112
        is ( C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ),
113
            0, "checkpw_ldap returns 0 if user lookup returns 0");
114
115
        $non_anonymous_bind_result = 'error';
116
        reload_ldap_module();
117
118
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
119
                               $dbh, 'hola', password => 'hey' ) }
120
                    qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/,
121
                    "checkpw_ldap prints correct warning if LDAP bind fails";
122
        is ( $ret, -1, "checkpw_ldap returns -1 LDAP bind fails for user (Bug 8148)");
123
    };
124
125
    subtest "auth_by_bind = 0 tests" => sub {
126
127
        plan tests => 8;
128
129
        $auth_by_bind = 0;
130
131
        # Anonymous bind
132
        $anonymous_bind            = 1;
133
        $desired_bind_result       = 'error';
134
        $non_anonymous_bind_result = 'error';
135
        reload_ldap_module();
136
137
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
138
                               $dbh, 'hola', password => 'hey' ) }
139
                    qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/,
140
                    "checkpw_ldap prints correct warning if LDAP bind fails";
141
        is ( $ret, 0, "checkpw_ldap returns 0 if bind fails");
142
143
        $anonymous_bind            = 1;
144
        $desired_bind_result       = 'success';
145
        $non_anonymous_bind_result = 'success';
146
        $desired_compare_result    = 'error';
147
        reload_ldap_module();
148
149
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
150
                               $dbh, 'hola', password => 'hey' ) }
151
                    qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/,
152
                    "checkpw_ldap prints correct warning if LDAP bind fails";
153
        is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)");
154
155
        # Non-anonymous bind
156
        $anonymous_bind            = 0;
157
        $desired_bind_result       = 'success';
158
        $non_anonymous_bind_result = 'error';
159
        $desired_compare_result    = 'dont care';
160
        reload_ldap_module();
161
162
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
163
                               $dbh, 'hola', password => 'hey' ) }
164
                    qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/,
165
                    "checkpw_ldap prints correct warning if LDAP bind fails";
166
        is ( $ret, 0, "checkpw_ldap returns 0 if bind fails");
167
168
        $anonymous_bind            = 0;
169
        $desired_bind_result       = 'success';
170
        $non_anonymous_bind_result = 'success';
171
        $desired_compare_result    = 'error';
172
        reload_ldap_module();
173
174
        warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap(
175
                               $dbh, 'hola', password => 'hey' ) }
176
                    qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/,
177
                    "checkpw_ldap prints correct warning if LDAP bind fails";
178
        is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)");
179
180
    };
181
};
182
183
subtest "search_method tests" => sub {
184
185
    plan tests => 5;
186
187
    my $ldap = mock_net_ldap();
188
189
    # Null params tests
190
    is( C4::Auth_with_ldap::search_method( $ldap, undef), undef,
191
        "search_method returns undef on undefined userid");
192
    is( C4::Auth_with_ldap::search_method( undef, "undef"), undef,
193
        "search_method returns undef on undefined ldap object");
194
195
    # search ->code and !->code
196
    $desired_search_result = 'error';
197
    reload_ldap_module();
198
    eval { $ret = C4::Auth_with_ldap::search_method( $ldap, "undef"); };
199
    like( $@, qr/LDAP search failed to return object : 1/,
200
        "search_method prints correct warning when db->search returns error code");
201
202
    $desired_search_result = 'success';
203
    $desired_count_result  = 2;
204
    reload_ldap_module();
205
    warning_like { $ret = C4::Auth_with_ldap::search_method( $ldap, '123') }
206
                   qr/^LDAP Auth rejected \: \(uid\=123\) gets 2 hits/,
207
                   "search_method prints correct warning when hits count is not 1";
208
    is( $ret, 0, "search_method returns 0 when hits count is not 1" );
209
210
};
211
212
# Function that mocks the call to C4::Context->config(param)
213
sub mockedC4Config {
214
215
    my $param =  shift;
216
217
    my %ldap_mapping = (
218
        firstname    => { is => 'givenname' },
219
        surname      => { is => 'sn' },
220
        address      => { is => 'postaladdress' },
221
        city         => { is => 'l' },
222
        zipcode      => { is => 'postalcode' },
223
        branchcode   => { is => 'branch' },
224
        userid       => { is => 'uid' },
225
        password     => { is => 'userpassword' },
226
        email        => { is => 'mail' },
227
        categorycode => { is => 'employeetype' },
228
        phone        => { is => 'telephonenumber' }
229
    );
230
231
    my %ldap_config  = (
232
        anonymous_bind => $anonymous_bind,
233
        auth_by_bind   => $auth_by_bind,
234
        base           => 'dc=metavore,dc=com',
235
        hostname       => 'localhost',
236
        mapping        => \%ldap_mapping,
237
        pass           => 'metavore',
238
        principal_name => '%s@my_domain.com',
239
        replicate      => $replicate,
240
        update         => $update,
241
        user           => 'cn=Manager,dc=metavore,dc=com'
242
    );
243
244
    return \%ldap_config;
245
};
246
247
# Function that mocks the call to Net::LDAP
248
sub mock_net_ldap {
249
250
    my $mocked_ldap = Test::MockObject->new();
251
252
    $mocked_ldap->mock( 'bind', sub {
253
254
        my @args = @_;
255
        my $mocked_message;
256
257
        if ( $#args > 1 ) {
258
            # Args passed => non-anonymous bind
259
            if ( $non_anonymous_bind_result eq 'error' ) {
260
                return mock_net_ldap_message(1,1,'error_name','error_text');
261
            } else {
262
                return mock_net_ldap_message(0,0,'','');
263
            }
264
        } else {
265
            $mocked_message = mock_net_ldap_message(
266
                ($desired_bind_result eq 'error' ) ? 1 : 0, # code
267
                ($desired_bind_result eq 'error' ) ? 1 : 0, # error
268
                ($desired_bind_result eq 'error' ) ? 'error_name' : 0, # error_name
269
                ($desired_bind_result eq 'error' ) ? 'error_text' : 0  # error_text
270
            );
271
        }
272
273
        return $mocked_message;
274
    });
275
276
    $mocked_ldap->mock( 'compare', sub {
277
278
        my $mocked_message;
279
280
        if ( $desired_compare_result eq 'error' ) {
281
            $mocked_message = mock_net_ldap_message(1,1,'error_name','error_text');
282
        } else {
283
            # we expect return code 6 for success
284
            $mocked_message = mock_net_ldap_message(6,0,'','');
285
        }
286
287
        return $mocked_message;
288
    });
289
290
    $mocked_ldap->mock( 'search', sub {
291
292
        return mock_net_ldap_search(
293
            ( $desired_count_result )             # count
294
                ? $desired_count_result
295
                : 1, # default to 1
296
            ( $desired_search_result eq 'error' ) # code
297
                ? 1
298
                : 0, # 0 == success
299
            ( $desired_search_result eq 'error' ) # error
300
                ? 1
301
                : 0,
302
            ( $desired_search_result eq 'error' ) # error_text
303
                ? 'error_text'
304
                : undef,
305
            ( $desired_search_result eq 'error' ) # error_name
306
                ? 'error_name'
307
                : undef,
308
            mock_net_ldap_entry( 'sampledn', 1 )  # shift_entry
309
        );
310
311
    });
312
313
    return $mocked_ldap;
314
}
315
316
sub mock_net_ldap_search {
317
    my ( $count, $code, $error, $error_text,
318
         $error_name, $shift_entry ) = @_;
319
320
    my $mocked_search = Test::MockObject->new();
321
    $mocked_search->mock( 'count',       sub { return $count; } );
322
    $mocked_search->mock( 'code',        sub { return $code; } );
323
    $mocked_search->mock( 'error',       sub { return $error; } );
324
    $mocked_search->mock( 'error_name',  sub { return $error_name; } );
325
    $mocked_search->mock( 'error_text',  sub { return $error_text; } );
326
    $mocked_search->mock( 'shift_entry', sub { return $shift_entry; } );
327
328
    return $mocked_search;
329
}
330
331
sub mock_net_ldap_message {
332
    my ( $code, $error, $error_name, $error_text ) = @_;
333
334
    my $mocked_message = Test::MockObject->new();
335
    $mocked_message->mock( 'code',       sub { $code } );
336
    $mocked_message->mock( 'error',      sub { $error } );
337
    $mocked_message->mock( 'error_name', sub { $error_name } );
338
    $mocked_message->mock( 'error_text', sub { $error_text } );
339
340
    return $mocked_message;
341
}
342
343
sub mock_net_ldap_entry {
344
    my ( $dn, $exists ) = @_;
345
346
    my $mocked_entry = Test::MockObject->new();
347
    $mocked_entry->mock( 'dn',     sub { return $dn; } );
348
    $mocked_entry->mock( 'exists', sub { return $exists } );
349
350
    return $mocked_entry;
351
}
352
353
# TODO: Once we remove the global variables in C4::Auth_with_ldap
354
# we shouldn't need this...
355
# ... Horrible hack
356
sub reload_ldap_module {
357
    delete $INC{'C4/Auth_with_ldap.pm'};
358
    require C4::Auth_with_ldap;
359
    C4::Auth_with_ldap->import;
360
}
361
362
$dbh->rollback;
363
364
1;

Return to bug 8148