Bug 25898: Prohibit indirect object notation
[koha.git] / t / Auth_with_shibboleth.t
blob622fd161fd8ca5f6723fbd17273b68ef21ef69ee
1 #!/usr/bin/perl
3 # This file is part of Koha.
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.
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.
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>.
18 use Modern::Perl;
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);
27 use utf8;
28 use CGI qw(-utf8 );
29 use C4::Context;
31 BEGIN {
32 if ( check_install( module => 'Test::DBIx::Class' ) ) {
33 plan tests => 17;
35 else {
36 plan skip_all => "Need Test::DBIx::Class";
40 use Test::DBIx::Class {
41 schema_class => 'Koha::Schema',
42 connect_info => [ 'dbi:SQLite:dbname=:memory:', '', '' ]
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' },
57 $ENV{'uid'} = "test1234";
58 $ENV{'sn'} = undef;
59 $ENV{'exp'} = undef;
60 $ENV{'cat'} = undef;
61 $ENV{'add'} = undef;
62 $ENV{'city'} = undef;
64 # Setup Mocks
65 ## Mock Context
66 my $context = Test::MockModule->new('C4::Context');
68 ### Mock ->config
69 $context->mock( 'config', \&mockedConfig );
71 ### Mock ->preference
72 my $OPACBaseURL = "testopac.com";
73 my $staffClientBaseURL = "teststaff.com";
74 $context->mock( 'preference', \&mockedPref );
76 ### Mock ->tz
77 $context->mock( 'timezone', sub { return 'local'; } );
79 ### Mock ->interface
80 my $interface = 'opac';
81 $context->mock( 'interface', \&mockedInterface );
83 ## Mock Database
84 my $database = Test::MockModule->new('Koha::Database');
86 ### Mock ->schema
87 $database->mock( 'schema', \&mockedSchema );
89 # Tests
90 ##############################################################
92 # Can module load
93 use C4::Auth_with_shibboleth;
94 require_ok('C4::Auth_with_shibboleth');
95 $C4::Auth_with_shibboleth::debug = '0';
97 # Subroutine tests
98 ## shib_ok
99 subtest "shib_ok tests" => sub {
100 plan tests => 5;
101 my $result;
103 # correct config, no debug
104 is( shib_ok(), '1', "good config" );
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" );
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" );
119 # add test for undefined shibboleth block
121 reset_config();
124 ## logout_shib
125 #my $query = CGI->new();
126 #is(logout_shib($query),"https://".$opac."/Shibboleth.sso/Logout?return="."https://".$opac,"logout_shib");
128 ## login_shib_url
129 subtest "login_shib_url tests" => sub {
130 plan tests => 2;
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);
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);
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"
149 my $post_params = 'user=bob&password=wideopen';
150 local $ENV{REQUEST_METHOD} = 'POST';
151 local $ENV{CONTENT_LENGTH} = length($post_params);
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;
159 open my $fh_read, '<', $infile or die "Could not open '$infile' $!";
161 $query = CGI->new($fh_read);
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"
170 close $fh_read;
173 ## get_login_shib
174 subtest "get_login_shib tests" => sub {
175 plan tests => 4;
176 my $login;
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" );
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"
193 "good config with debug enabled, correct warnings received";
194 is( $login, "test1234",
195 "good config with debug enabled, attribute value returned" );
197 # bad config - with shib_ok implemented, we should never reach this sub with a bad config
200 ## checkpw_shib
201 subtest "checkpw_shib tests" => sub {
202 plan tests => 24;
204 my $shib_login;
205 my ( $retval, $retcard, $retuserid );
207 # Setup Mock Database Data
208 fixtures_ok [
209 'Borrower' => [
210 [qw/cardnumber userid surname address city email/],
211 [qw/testcardnumber test1234 renvoize myaddress johnston /],
212 [qw/testcardnumber1 test12345 clamp1 myaddress quechee kid@clamp.io/],
213 [qw/testcardnumber2 test123456 clamp2 myaddress quechee kid@clamp.io/],
215 'Category' => [ [qw/categorycode default_privacy/], [qw/S never/], ]
217 'Installed some custom fixtures via the Populate fixture class';
219 # debug off
220 $C4::Auth_with_shibboleth::debug = '0';
222 # good user
223 $shib_login = "test1234";
224 warnings_are {
225 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
227 [], "good user with no debug";
228 is( $retval, "1", "user authenticated" );
229 is( $retcard, "testcardnumber", "expected cardnumber returned" );
230 is( $retuserid, "test1234", "expected userid returned" );
232 # bad user
233 $shib_login = 'martin';
234 warnings_are {
235 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
237 [], "bad user with no debug";
238 is( $retval, "0", "user not authenticated" );
240 # duplicated matchpoint
241 $matchpoint = 'email';
242 $mapping{'email'} = { is => 'email' };
243 $shib_login = 'kid@clamp.io';
244 warnings_are {
245 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
247 [], "bad user with no debug";
248 is( $retval, "0", "user not authenticated if duplicated matchpoint" );
249 $C4::Auth_with_shibboleth::debug = '1';
250 warnings_are {
251 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
254 q/checkpw_shib/,
255 q/koha borrower field to match: email/,
256 q/shibboleth attribute to match: email/,
257 q/User Shibboleth-authenticated as: kid@clamp.io/,
258 q/There are several users with email of kid@clamp.io, matchpoints must be unique/
259 ], "duplicated matchpoint warned with debug";
260 $C4::Auth_with_shibboleth::debug = '0';
261 reset_config();
263 # autocreate user
264 $autocreate = 1;
265 $shib_login = 'test4321';
266 $ENV{'uid'} = 'test4321';
267 $ENV{'sn'} = "pika";
268 $ENV{'exp'} = "2017";
269 $ENV{'cat'} = "S";
270 $ENV{'add'} = 'Address';
271 $ENV{'city'} = 'City';
272 warnings_are {
273 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
275 [], "new user added with no debug";
276 is( $retval, "1", "user authenticated" );
277 is( $retuserid, "test4321", "expected userid returned" );
278 ok my $new_user = ResultSet('Borrower')
279 ->search( { 'userid' => 'test4321' }, { rows => 1 } ), "new user found";
280 is_fields [qw/surname dateexpiry address city/], $new_user->next,
281 [qw/pika 2017 Address City/],
282 'Found $new_users surname';
283 $autocreate = 0;
285 # sync user
286 $sync = 1;
287 $ENV{'city'} = 'AnotherCity';
288 warnings_are {
289 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
291 [], "good user with sync";
293 ok my $sync_user = ResultSet('Borrower')
294 ->search( { 'userid' => 'test4321' }, { rows => 1 } ), "sync user found";
296 is_fields [qw/surname dateexpiry address city/], $sync_user->next,
297 [qw/pika 2017 Address AnotherCity/],
298 'Found $sync_user synced city';
299 $sync = 0;
301 # debug on
302 $C4::Auth_with_shibboleth::debug = '1';
304 # good user
305 $shib_login = "test1234";
306 warnings_exist {
307 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
310 qr/checkpw_shib/,
311 qr/koha borrower field to match: userid/,
312 qr/shibboleth attribute to match: uid/,
313 qr/User Shibboleth-authenticated as:/
315 "good user with debug enabled";
316 is( $retval, "1", "user authenticated" );
317 is( $retcard, "testcardnumber", "expected cardnumber returned" );
318 is( $retuserid, "test1234", "expected userid returned" );
320 # bad user
321 $shib_login = "martin";
322 warnings_exist {
323 ( $retval, $retcard, $retuserid ) = checkpw_shib($shib_login);
326 qr/checkpw_shib/,
327 qr/koha borrower field to match: userid/,
328 qr/shibboleth attribute to match: uid/,
329 qr/User Shibboleth-authenticated as:/,
330 qr/not a valid Koha user/
332 "bad user with debug enabled";
333 is( $retval, "0", "user not authenticated" );
337 ## _get_uri - opac
338 $OPACBaseURL = "testopac.com";
339 is( C4::Auth_with_shibboleth::_get_uri(),
340 "https://testopac.com", "https opac uri returned" );
342 $OPACBaseURL = "http://testopac.com";
343 my $result;
344 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }[
345 "shibboleth interface: $interface",
346 "Shibboleth requires OPACBaseURL/staffClientBaseURL to use the https protocol!"
348 "improper protocol - received expected warning";
349 is( $result, "https://testopac.com", "https opac uri returned" );
351 $OPACBaseURL = "https://testopac.com";
352 is( C4::Auth_with_shibboleth::_get_uri(),
353 "https://testopac.com", "https opac uri returned" );
355 $OPACBaseURL = undef;
356 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }
357 [ "shibboleth interface: $interface", "OPACBaseURL not set!" ],
358 "undefined OPACBaseURL - received expected warning";
359 is( $result, "https://", "https $interface uri returned" );
361 ## _get_uri - intranet
362 $interface = 'intranet';
363 $staffClientBaseURL = "teststaff.com";
364 is( C4::Auth_with_shibboleth::_get_uri(),
365 "https://teststaff.com", "https $interface uri returned" );
367 $staffClientBaseURL = "http://teststaff.com";
368 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }[
369 "shibboleth interface: $interface",
370 "Shibboleth requires OPACBaseURL/staffClientBaseURL to use the https protocol!"
372 "improper protocol - received expected warning";
373 is( $result, "https://teststaff.com", "https $interface uri returned" );
375 $staffClientBaseURL = "https://teststaff.com";
376 is( C4::Auth_with_shibboleth::_get_uri(),
377 "https://teststaff.com", "https $interface uri returned" );
379 $staffClientBaseURL = undef;
380 warnings_are { $result = C4::Auth_with_shibboleth::_get_uri() }
381 [ "shibboleth interface: $interface", "staffClientBaseURL not set!" ],
382 "undefined staffClientBaseURL - received expected warning";
383 is( $result, "https://", "https $interface uri returned" );
385 ## _get_shib_config
386 # Internal helper function, covered in tests above
388 sub mockedConfig {
389 my $param = shift;
391 my %shibboleth = (
392 'autocreate' => $autocreate,
393 'sync' => $sync,
394 'matchpoint' => $matchpoint,
395 'mapping' => \%mapping
398 return \%shibboleth;
401 sub mockedPref {
402 my $param = $_[1];
403 my $return;
405 if ( $param eq 'OPACBaseURL' ) {
406 $return = $OPACBaseURL;
409 if ( $param eq 'staffClientBaseURL' ) {
410 $return = $staffClientBaseURL;
413 return $return;
416 sub mockedInterface {
417 return $interface;
420 sub mockedSchema {
421 return Schema();
424 ## Convenience method to reset config
425 sub reset_config {
426 $matchpoint = 'userid';
427 $autocreate = 0;
428 $sync = 0;
429 %mapping = (
430 'userid' => { 'is' => 'uid' },
431 'surname' => { 'is' => 'sn' },
432 'dateexpiry' => { 'is' => 'exp' },
433 'categorycode' => { 'is' => 'cat' },
434 'address' => { 'is' => 'add' },
435 'city' => { 'is' => 'city' },
437 $ENV{'uid'} = "test1234";
438 $ENV{'sn'} = undef;
439 $ENV{'exp'} = undef;
440 $ENV{'cat'} = undef;
441 $ENV{'add'} = undef;
442 $ENV{'city'} = undef;
444 return 1;