Bug 25133: Handle 12hr format for dt_from_string
[koha.git] / t / Auth_with_shibboleth.t
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 $| = 1;
21 use Module::Load::Conditional qw/check_install/;
22 use Test::More;
23 use Test::MockModule;
24 use Test::Warn;
25 use File::Temp qw(tempdir);
26
27 use utf8;
28 use CGI qw(-utf8 );
29 use C4::Context;
30
31 BEGIN {
32     if ( check_install( module => 'Test::DBIx::Class' ) ) {
33         plan tests => 17;
34     }
35     else {
36         plan skip_all => "Need Test::DBIx::Class";
37     }
38 }
39
40 use Test::DBIx::Class {
41     schema_class => 'Koha::Schema',
42     connect_info => [ 'dbi:SQLite:dbname=:memory:', '', '' ]
43 };
44
45 # Mock Variables
46 my $matchpoint = 'userid';
47 my $autocreate = 0;
48 my $sync = 0;
49 my %mapping    = (
50     'userid'       => { 'is' => 'uid' },
51     'surname'      => { 'is' => 'sn' },
52     'dateexpiry'   => { 'is' => 'exp' },
53     'categorycode' => { 'is' => 'cat' },
54     'address'      => { 'is' => 'add' },
55     'city'         => { 'is' => 'city' },
56 );
57 $ENV{'uid'}  = "test1234";
58 $ENV{'sn'}   = undef;
59 $ENV{'exp'}  = undef;
60 $ENV{'cat'}  = undef;
61 $ENV{'add'}  = undef;
62 $ENV{'city'} = undef;
63
64 # Setup Mocks
65 ## Mock Context
66 my $context = new Test::MockModule('C4::Context');
67
68 ### Mock ->config
69 $context->mock( 'config', \&mockedConfig );
70
71 ### Mock ->preference
72 my $OPACBaseURL = "testopac.com";
73 my $staffClientBaseURL = "teststaff.com";
74 $context->mock( 'preference', \&mockedPref );
75
76 ### Mock ->tz
77 $context->mock( 'timezone', sub { return 'local'; } );
78
79 ### Mock ->interface
80 my $interface = 'opac';
81 $context->mock( 'interface', \&mockedInterface );
82
83 ## Mock Database
84 my $database = new Test::MockModule('Koha::Database');
85
86 ### Mock ->schema
87 $database->mock( 'schema', \&mockedSchema );
88
89 # Tests
90 ##############################################################
91
92 # Can module load
93 use C4::Auth_with_shibboleth;
94 require_ok('C4::Auth_with_shibboleth');
95 $C4::Auth_with_shibboleth::debug = '0';
96
97 # Subroutine tests
98 ## shib_ok
99 subtest "shib_ok tests" => sub {
100     plan tests => 5;
101     my $result;
102
103     # correct config, no debug
104     is( shib_ok(), '1', "good config" );
105
106     # bad config, no debug
107     $matchpoint = undef;
108     warnings_are { $result = shib_ok() }
109     [ { carped => 'shibboleth matchpoint not defined' }, ],
110       "undefined matchpoint = fatal config, warning given";
111     is( $result, '0', "bad config" );
112
113     $matchpoint = 'email';
114     warnings_are { $result = shib_ok() }
115     [ { carped => 'shibboleth matchpoint not mapped' }, ],
116       "unmapped matchpoint = fatal config, warning given";
117     is( $result, '0', "bad config" );
118
119     # add test for undefined shibboleth block
120
121     reset_config();
122 };
123
124 ## logout_shib
125 #my $query = CGI->new();
126 #is(logout_shib($query),"https://".$opac."/Shibboleth.sso/Logout?return="."https://".$opac,"logout_shib");
127
128 ## login_shib_url
129 subtest "login_shib_url tests" => sub {
130     plan tests => 2;
131
132     my $string = 'language=en-GB&param="heh❤"';
133     my $query_string = Encode::encode('UTF-8', $string);
134     my $query_string_uri_escaped = URI::Escape::uri_escape_utf8('?'.$string);
135
136     local $ENV{REQUEST_METHOD} = 'GET';
137     local $ENV{QUERY_STRING}   = $query_string;
138     local $ENV{SCRIPT_NAME}    = '/cgi-bin/koha/opac-user.pl';
139     my $query = CGI->new($query_string);
140     is(
141         login_shib_url($query),
142         'https://testopac.com'
143           . '/Shibboleth.sso/Login?target='
144           . 'https://testopac.com/cgi-bin/koha/opac-user.pl'
145           . $query_string_uri_escaped,
146         "login shib url"
147     );
148
149     my $post_params = 'user=bob&password=wideopen';
150     local $ENV{REQUEST_METHOD} = 'POST';
151     local $ENV{CONTENT_LENGTH} = length($post_params);
152
153     my $dir = tempdir( CLEANUP => 1 );
154     my $infile = "$dir/in.txt";
155     open my $fh_write, '>', $infile or die "Could not open '$infile' $!";
156     print $fh_write $post_params;
157     close $fh_write;
158
159     open my $fh_read, '<', $infile or die "Could not open '$infile' $!";
160
161     $query = CGI->new($fh_read);
162     is(
163         login_shib_url($query),
164         'https://testopac.com'
165           . '/Shibboleth.sso/Login?target='
166           . 'https://testopac.com/cgi-bin/koha/opac-user.pl',
167         "login shib url"
168     );
169
170     close $fh_read;
171 };
172
173 ## get_login_shib
174 subtest "get_login_shib tests" => sub {
175     plan tests => 4;
176     my $login;
177
178     # good config
179     ## debug off
180     $C4::Auth_with_shibboleth::debug = '0';
181     warnings_are { $login = get_login_shib() }[],
182       "good config with debug off, no warnings received";
183     is( $login, "test1234",
184         "good config with debug off, attribute value returned" );
185
186     ## debug on
187     $C4::Auth_with_shibboleth::debug = '1';
188     warnings_are { $login = get_login_shib() }[
189         "koha borrower field to match: userid",
190         "shibboleth attribute to match: uid",
191         "uid value: test1234"
192     ],
193       "good config with debug enabled, correct warnings received";
194     is( $login, "test1234",
195         "good config with debug enabled, attribute value returned" );
196
197 # bad config - with shib_ok implemented, we should never reach this sub with a bad config
198 };
199
200 ## checkpw_shib
201 subtest "checkpw_shib tests" => sub {
202     plan tests => 21;
203
204     my $shib_login;
205     my ( $retval, $retcard, $retuserid );
206
207     # Setup Mock Database Data
208     fixtures_ok [
209         'Borrower' => [
210             [qw/cardnumber userid surname address city/],
211             [qw/testcardnumber test1234 renvoize myaddress johnston/],
212         ],
213         'Category' => [ [qw/categorycode default_privacy/], [qw/S never/], ]
214       ],
215       'Installed some custom fixtures via the Populate fixture class';
216
217     # debug off
218     $C4::Auth_with_shibboleth::debug = '0';
219
220     # good user
221     $shib_login = "test1234";
222     warnings_are {
223         ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
224     }
225     [], "good user with no debug";
226     is( $retval,    "1",              "user authenticated" );
227     is( $retcard,   "testcardnumber", "expected cardnumber returned" );
228     is( $retuserid, "test1234",       "expected userid returned" );
229
230     # bad user
231     $shib_login = 'martin';
232     warnings_are {
233         ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
234     }
235     [], "bad user with no debug";
236     is( $retval, "0", "user not authenticated" );
237
238     # autocreate user
239     $autocreate  = 1;
240     $shib_login  = 'test4321';
241     $ENV{'uid'}  = 'test4321';
242     $ENV{'sn'}   = "pika";
243     $ENV{'exp'}  = "2017";
244     $ENV{'cat'}  = "S";
245     $ENV{'add'}  = 'Address';
246     $ENV{'city'} = 'City';
247     warnings_are {
248         ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
249     }
250     [], "new user added with no debug";
251     is( $retval,    "1",        "user authenticated" );
252     is( $retuserid, "test4321", "expected userid returned" );
253     ok my $new_user = ResultSet('Borrower')
254       ->search( { 'userid' => 'test4321' }, { rows => 1 } ), "new user found";
255     is_fields [qw/surname dateexpiry address city/], $new_user->next,
256       [qw/pika 2017 Address City/],
257       'Found $new_users surname';
258     $autocreate = 0;
259
260     # sync user
261     $sync = 1;
262     $ENV{'city'} = 'AnotherCity';
263     warnings_are {
264         ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
265     }
266     [], "good user with sync";
267
268     ok my $sync_user = ResultSet('Borrower')
269       ->search( { 'userid' => 'test4321' }, { rows => 1 } ), "sync user found";
270
271     is_fields [qw/surname dateexpiry address city/], $sync_user->next,
272       [qw/pika 2017 Address AnotherCity/],
273       'Found $sync_user synced city';
274     $sync = 0;
275
276     # debug on
277     $C4::Auth_with_shibboleth::debug = '1';
278
279     # good user
280     $shib_login = "test1234";
281     warnings_exist {
282         ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
283     }
284     [
285         qr/checkpw_shib/,
286         qr/koha borrower field to match: userid/,
287         qr/shibboleth attribute to match: uid/,
288         qr/User Shibboleth-authenticated as:/
289     ],
290       "good user with debug enabled";
291     is( $retval,    "1",              "user authenticated" );
292     is( $retcard,   "testcardnumber", "expected cardnumber returned" );
293     is( $retuserid, "test1234",       "expected userid returned" );
294
295     # bad user
296     $shib_login = "martin";
297     warnings_exist {
298         ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
299     }
300     [
301         qr/checkpw_shib/,
302         qr/koha borrower field to match: userid/,
303         qr/shibboleth attribute to match: uid/,
304         qr/User Shibboleth-authenticated as:/,
305         qr/not a valid Koha user/
306     ],
307       "bad user with debug enabled";
308     is( $retval, "0", "user not authenticated" );
309
310 };
311
312 ## _get_uri - opac
313 $OPACBaseURL = "testopac.com";
314 is( C4::Auth_with_shibboleth::_get_uri(),
315     "https://testopac.com", "https opac uri returned" );
316
317 $OPACBaseURL = "http://testopac.com";
318 my $result;
319 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }[
320     "shibboleth interface: $interface",
321 "Shibboleth requires OPACBaseURL/staffClientBaseURL to use the https protocol!"
322 ],
323   "improper protocol - received expected warning";
324 is( $result, "https://testopac.com", "https opac uri returned" );
325
326 $OPACBaseURL = "https://testopac.com";
327 is( C4::Auth_with_shibboleth::_get_uri(),
328     "https://testopac.com", "https opac uri returned" );
329
330 $OPACBaseURL = undef;
331 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }
332 [ "shibboleth interface: $interface", "OPACBaseURL not set!" ],
333   "undefined OPACBaseURL - received expected warning";
334 is( $result, "https://", "https $interface uri returned" );
335
336 ## _get_uri - intranet
337 $interface = 'intranet';
338 $staffClientBaseURL = "teststaff.com";
339 is( C4::Auth_with_shibboleth::_get_uri(),
340     "https://teststaff.com", "https $interface uri returned" );
341
342 $staffClientBaseURL = "http://teststaff.com";
343 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }[
344     "shibboleth interface: $interface",
345 "Shibboleth requires OPACBaseURL/staffClientBaseURL to use the https protocol!"
346 ],
347   "improper protocol - received expected warning";
348 is( $result, "https://teststaff.com", "https $interface uri returned" );
349
350 $staffClientBaseURL = "https://teststaff.com";
351 is( C4::Auth_with_shibboleth::_get_uri(),
352     "https://teststaff.com", "https $interface uri returned" );
353
354 $staffClientBaseURL = undef;
355 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }
356 [ "shibboleth interface: $interface", "staffClientBaseURL not set!" ],
357   "undefined staffClientBaseURL - received expected warning";
358 is( $result, "https://", "https $interface uri returned" );
359
360 ## _get_shib_config
361 # Internal helper function, covered in tests above
362
363 sub mockedConfig {
364     my $param = shift;
365
366     my %shibboleth = (
367         'autocreate' => $autocreate,
368         'sync'       => $sync,
369         'matchpoint' => $matchpoint,
370         'mapping'    => \%mapping
371     );
372
373     return \%shibboleth;
374 }
375
376 sub mockedPref {
377     my $param = $_[1];
378     my $return;
379
380     if ( $param eq 'OPACBaseURL' ) {
381         $return = $OPACBaseURL;
382     }
383
384     if ( $param eq 'staffClientBaseURL' ) {
385         $return = $staffClientBaseURL;
386     }
387
388     return $return;
389 }
390
391 sub mockedInterface {
392     return $interface;
393 }
394
395 sub mockedSchema {
396     return Schema();
397 }
398
399 ## Convenience method to reset config
400 sub reset_config {
401     $matchpoint = 'userid';
402     $autocreate = 0;
403     $sync = 0;
404     %mapping    = (
405         'userid'       => { 'is' => 'uid' },
406         'surname'      => { 'is' => 'sn' },
407         'dateexpiry'   => { 'is' => 'exp' },
408         'categorycode' => { 'is' => 'cat' },
409         'address'      => { 'is' => 'add' },
410         'city'         => { 'is' => 'city' },
411     );
412     $ENV{'uid'}  = "test1234";
413     $ENV{'sn'}   = undef;
414     $ENV{'exp'}  = undef;
415     $ENV{'cat'}  = undef;
416     $ENV{'add'}  = undef;
417     $ENV{'city'} = undef;
418
419     return 1;
420 }
421