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

(-)a/C4/Auth.pm (-4 / +10 lines)
Lines 791-796 sub checkauth { Link Here
791
    my $type            = shift;
791
    my $type            = shift;
792
    my $emailaddress    = shift;
792
    my $emailaddress    = shift;
793
    my $template_name   = shift;
793
    my $template_name   = shift;
794
    my $params          = shift || {}; # do_not_print
794
    $type = 'opac' unless $type;
795
    $type = 'opac' unless $type;
795
796
796
    if ( $type eq 'opac' && !C4::Context->preference("OpacPublic") ) {
797
    if ( $type eq 'opac' && !C4::Context->preference("OpacPublic") ) {
Lines 1332-1339 sub checkauth { Link Here
1332
            $uri->query_param_delete('userid');
1333
            $uri->query_param_delete('userid');
1333
            $uri->query_param_delete('password');
1334
            $uri->query_param_delete('password');
1334
            $uri->query_param_delete('koha_login_context');
1335
            $uri->query_param_delete('koha_login_context');
1335
            print $query->redirect(-uri => $uri->as_string, -cookie => $cookie, -status=>'303 See other');
1336
            unless ( $params->{do_not_print} ) {
1336
            safe_exit;
1337
                print $query->redirect(-uri => $uri->as_string, -cookie => $cookie, -status=>'303 See other');
1338
                safe_exit;
1339
            }
1337
        }
1340
        }
1338
1341
1339
        return ( $userid, $cookie, $sessionID, $flags );
1342
        return ( $userid, $cookie, $sessionID, $flags );
Lines 1481-1488 sub checkauth { Link Here
1481
    );
1484
    );
1482
    $template->param(%info);
1485
    $template->param(%info);
1483
1486
1484
    #    $cookie = $query->cookie(CGISESSID => $session->id
1487
    if ( $params->{do_not_print} ) {
1485
    #   );
1488
        # This must be used for testing purpose only!
1489
        return ( undef, undef, undef, undef, $template );
1490
    }
1491
1486
    print $query->header(
1492
    print $query->header(
1487
        {   type              => 'text/html',
1493
        {   type              => 'text/html',
1488
            charset           => 'utf-8',
1494
            charset           => 'utf-8',
(-)a/t/db_dependent/Auth.t (-111 / +54 lines)
Lines 14-20 use t::lib::TestBuilder; Link Here
14
14
15
use C4::Auth;
15
use C4::Auth;
16
use C4::Members;
16
use C4::Members;
17
use Koha::AuthUtils qw/hash_password/;
17
use Koha::AuthUtils qw( hash_password );
18
use Koha::DateUtils qw( dt_from_string );
18
use Koha::Database;
19
use Koha::Database;
19
use Koha::Patrons;
20
use Koha::Patrons;
20
use Koha::Auth::TwoFactorAuth;
21
use Koha::Auth::TwoFactorAuth;
Lines 87-95 subtest 'checkauth() tests' => sub { Link Here
87
88
88
        my $patron = $builder->build_object(
89
        my $patron = $builder->build_object(
89
            { class => 'Koha::Patrons', value => { flags => 1 } } );
90
            { class => 'Koha::Patrons', value => { flags => 1 } } );
90
        my $password = 'password';
91
        my $password = set_weak_password($patron);
91
        t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
92
        $patron->set_password( { password => $password } );
93
        $cgi = Test::MockObject->new();
92
        $cgi = Test::MockObject->new();
94
        $cgi->mock( 'cookie', sub { return; } );
93
        $cgi->mock( 'cookie', sub { return; } );
95
        $cgi->mock(
94
        $cgi->mock(
Lines 145-183 subtest 'checkauth() tests' => sub { Link Here
145
144
146
        my $password_expired;
145
        my $password_expired;
147
146
148
        my $patron_class = Test::MockModule->new('Koha::Patron');
147
        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
149
        $patron_class->mock( 'password_expired', sub { return $password_expired; } );
150
151
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 1 } });
152
        my $password = 'password';
153
        t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
154
        $patron->set_password( { password => $password } );
155
156
        my $cgi_mock = Test::MockModule->new('CGI')->mock( 'request_method', 'POST' );
157
        my $cgi = CGI->new;
158
        $cgi->param( -name => 'userid',   -value => $patron->userid );
159
        $cgi->param( -name => 'password', -value => $password );
160
148
161
        my $auth = Test::MockModule->new( 'C4::Auth' );
149
        my $password = set_weak_password($patron);
162
        # Tests will fail if we hit safe_exit
150
        $patron->password_expiration_date( dt_from_string->subtract(days => 1) )->store;
163
        $auth->mock( 'safe_exit', sub { return } );
164
151
165
        my ( $userid, $cookie, $sessionID, $flags );
152
        my $cgi_mock = Test::MockModule->new('CGI');
153
        $cgi_mock->mock( 'request_method', sub { return 'POST' } );
154
        my $cgi  = CGI->new;
155
        # Simulating the login form submission
156
        $cgi->param( 'userid',   $patron->userid );
157
        $cgi->param( 'password', $password );
166
158
167
        {
159
        my ( $userid, $cookie, $sessionID, $flags, $template ) = C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
168
            t::lib::Mocks::mock_preference( 'DumpTemplateVarsOpac', 1 );
160
        is( $template->{VARS}->{password_has_expired}, 1 );
169
            # checkauth will redirect and safe_exit if not authenticated and not authorized
170
            local *STDOUT;
171
            my $stdout;
172
            open STDOUT, '>', \$stdout;
173
174
            # Password has expired
175
            $password_expired = 1;
176
            C4::Auth::checkauth( $cgi, 0, { catalogue => 1 } );
177
            like( $stdout, qr{'password_has_expired' => 1}, 'password_has_expired is set to 1' );
178
179
            close STDOUT;
180
        };
181
    };
161
    };
182
162
183
    subtest 'Reset auth state when changing users' => sub {
163
    subtest 'Reset auth state when changing users' => sub {
Lines 201-208 subtest 'checkauth() tests' => sub { Link Here
201
            C4::Auth::check_cookie_auth( $sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
181
            C4::Auth::check_cookie_auth( $sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
202
        is( $return, 'ok', 'Patron authenticated' );
182
        is( $return, 'ok', 'Patron authenticated' );
203
183
204
        my $mock1 = Test::MockModule->new('C4::Auth');
205
        $mock1->mock( 'safe_exit', sub { return 'safe_exit_redirect' } );
206
        my $mock2 = Test::MockModule->new('CGI');
184
        my $mock2 = Test::MockModule->new('CGI');
207
        $mock2->mock( 'request_method', 'POST' );
185
        $mock2->mock( 'request_method', 'POST' );
208
        $mock2->mock( 'cookie',         sub { return $sessionID; } );    # oversimplified..
186
        $mock2->mock( 'cookie',         sub { return $sessionID; } );    # oversimplified..
Lines 211-231 subtest 'checkauth() tests' => sub { Link Here
211
        $cgi->param( -name => 'userid',             -value => 'Bond' );
189
        $cgi->param( -name => 'userid',             -value => 'Bond' );
212
        $cgi->param( -name => 'password',           -value => 'James Bond' );
190
        $cgi->param( -name => 'password',           -value => 'James Bond' );
213
        $cgi->param( -name => 'koha_login_context', -value => 1 );
191
        $cgi->param( -name => 'koha_login_context', -value => 1 );
214
        my ( @return, $stdout );
192
        my ( $userid, $cookie, $flags, $template );
215
        {
193
        ( $userid, $cookie, $sessionID, $flags, $template ) =
216
            local *STDOUT;
194
            C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
217
            local %ENV;
195
        is( $template->{VARS}->{loginprompt}, 1, 'Changing to non-existent user causes a redirect to login' );
218
            $ENV{REMOTE_ADDR} = '1.2.3.4';
219
            open STDOUT, '>', \$stdout;
220
            @return = C4::Auth::checkauth( $cgi, 0, {} );
221
            close STDOUT;
222
        }
223
        is( $return[0], 'safe_exit_redirect', 'Changing to non-existent user causes a redirect to login' );
224
    };
196
    };
225
197
226
227
    subtest 'While still logged in, relogin with another user' => sub {
198
    subtest 'While still logged in, relogin with another user' => sub {
228
        plan tests => 6;
199
        plan tests => 5;
229
200
230
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {} });
201
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {} });
231
        my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => {} });
202
        my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => {} });
Lines 237-272 subtest 'checkauth() tests' => sub { Link Here
237
        $session->param( 'lasttime',     time() );
208
        $session->param( 'lasttime',     time() );
238
        $session->param( 'interface',    'opac' );
209
        $session->param( 'interface',    'opac' );
239
        $session->flush;
210
        $session->flush;
240
        my $sessionID = $session->id;
211
        my $previous_sessionID = $session->id;
241
        C4::Context->_new_userenv($sessionID);
212
        C4::Context->_new_userenv($previous_sessionID);
242
213
243
        my ( $return ) = C4::Auth::check_cookie_auth( $sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
214
        my ( $return ) = C4::Auth::check_cookie_auth( $previous_sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
244
        is( $return, 'ok', 'Former session in shape now' );
215
        is( $return, 'ok', 'Former session in shape now' );
245
216
246
        my $mock1 = Test::MockModule->new('C4::Auth');
217
        my $mock1 = Test::MockModule->new('C4::Auth');
247
        $mock1->mock( 'safe_exit', sub {} );
218
        $mock1->mock( 'safe_exit', sub {} );
248
        my $mock2 = Test::MockModule->new('CGI');
219
        my $mock2 = Test::MockModule->new('CGI');
249
        $mock2->mock( 'request_method', 'POST' );
220
        $mock2->mock( 'request_method', 'POST' );
250
        $mock2->mock( 'cookie', sub { return $sessionID; } ); # oversimplified..
221
        $mock2->mock( 'cookie', sub { return $previous_sessionID; } ); # oversimplified..
251
        my $cgi = CGI->new;
222
        my $cgi = CGI->new;
252
        my $password = 'Incr3d1blyZtr@ng93$';
223
        my $password = 'Incr3d1blyZtr@ng93$';
253
        $patron2->set_password({ password => $password });
224
        $patron2->set_password({ password => $password });
254
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
225
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
255
        $cgi->param( -name => 'password',           -value => $password );
226
        $cgi->param( -name => 'password',           -value => $password );
256
        $cgi->param( -name => 'koha_login_context', -value => 1 );
227
        $cgi->param( -name => 'koha_login_context', -value => 1 );
257
        my ( @return, $stdout );
228
        my ( $userid, $cookie, $sessionID, $flags, $template ) =
258
        {
229
            C4::Auth::checkauth( $cgi, 0, {}, 'opac', undef, undef, { do_not_print => 1 } );
259
            local *STDOUT;
230
        is( $userid, $patron2->userid, 'Login of patron2 approved' );
260
            local %ENV;
231
        isnt( $sessionID, $previous_sessionID, 'Did not return previous session ID' );
261
            $ENV{REMOTE_ADDR} = '1.2.3.4';
232
        ok( $sessionID, 'New session ID not empty' );
262
            open STDOUT, '>', \$stdout;
263
            @return = C4::Auth::checkauth( $cgi, 0, {} );
264
            close STDOUT;
265
        }
266
        # Note: We can test return values from checkauth here since we mocked the safe_exit after the Redirect 303
267
        is( $return[0], $patron2->userid, 'Login of patron2 approved' );
268
        isnt( $return[2], $sessionID, 'Did not return previous session ID' );
269
        ok( $return[2], 'New session ID not empty' );
270
233
271
        # Similar situation: Relogin with former session of $patron, new user $patron2 has no permissions
234
        # Similar situation: Relogin with former session of $patron, new user $patron2 has no permissions
272
        $patron2->flags(undef)->store;
235
        $patron2->flags(undef)->store;
Lines 274-295 subtest 'checkauth() tests' => sub { Link Here
274
        $session->param( 'id',           $patron->userid );
237
        $session->param( 'id',           $patron->userid );
275
        $session->param( 'interface',    'intranet' );
238
        $session->param( 'interface',    'intranet' );
276
        $session->flush;
239
        $session->flush;
277
        $sessionID = $session->id;
240
        $previous_sessionID = $session->id;
278
        C4::Context->_new_userenv($sessionID);
241
        C4::Context->_new_userenv($previous_sessionID);
279
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
242
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
280
        $cgi->param( -name => 'password',           -value => $password );
243
        $cgi->param( -name => 'password',           -value => $password );
281
        $cgi->param( -name => 'koha_login_context', -value => 1 );
244
        $cgi->param( -name => 'koha_login_context', -value => 1 );
282
        {
245
        ( $userid, $cookie, $sessionID, $flags, $template ) =
283
            local *STDOUT;
246
            C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
284
            local %ENV;
247
        is( $template->{VARS}->{nopermission}, 1, 'No permission response' );
285
            $ENV{REMOTE_ADDR} = '1.2.3.4';
286
            $stdout = q{};
287
            open STDOUT, '>', \$stdout;
288
            @return = C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet' ); # patron2 has no catalogue perm
289
            close STDOUT;
290
        }
291
        like( $stdout, qr/You do not have permission to access this page/, 'No permission response' );
292
        is( @return, 0, 'checkauth returned failure' );
293
    };
248
    };
294
249
295
    subtest 'Two-factor authentication' => sub {
250
    subtest 'Two-factor authentication' => sub {
Lines 408-416 subtest 'checkauth() tests' => sub { Link Here
408
363
409
        my $branch = $builder->build_object({ class => 'Koha::Libraries' });
364
        my $branch = $builder->build_object({ class => 'Koha::Libraries' });
410
365
411
        my $password = 'password';
366
        my $password = set_weak_password($staff_user);
412
        t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
413
        $staff_user->set_password( { password => $password } );
414
        my $cgi = Test::MockObject->new();
367
        my $cgi = Test::MockObject->new();
415
        $cgi->mock( 'cookie', sub { return; } );
368
        $cgi->mock( 'cookie', sub { return; } );
416
        $cgi->mock(
369
        $cgi->mock(
Lines 449-458 subtest 'no_set_userenv parameter tests' => sub { Link Here
449
402
450
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
403
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
451
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
404
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
452
    my $password = 'password';
453
405
454
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
406
    my $password = set_weak_password($patron);
455
    $patron->set_password({ password => $password });
456
407
457
    ok( checkpw( $patron->userid, $password, undef, undef, 1 ), 'checkpw returns true' );
408
    ok( checkpw( $patron->userid, $password, undef, undef, 1 ), 'checkpw returns true' );
458
    is( C4::Context->userenv, undef, 'Userenv should be undef as required' );
409
    is( C4::Context->userenv, undef, 'Userenv should be undef as required' );
Lines 471-480 subtest 'checkpw lockout tests' => sub { Link Here
471
422
472
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
423
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
473
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
424
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
474
    my $password = 'password';
425
    my $password = set_weak_password($patron);
475
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
476
    t::lib::Mocks::mock_preference( 'FailedLoginAttempts', 1 );
426
    t::lib::Mocks::mock_preference( 'FailedLoginAttempts', 1 );
477
    $patron->set_password({ password => $password });
478
427
479
    my ( $checkpw, undef, undef ) = checkpw( $patron->cardnumber, $password, undef, undef, 1 );
428
    my ( $checkpw, undef, undef ) = checkpw( $patron->cardnumber, $password, undef, undef, 1 );
480
    ok( $checkpw, 'checkpw returns true with right password when logging in via cardnumber' );
429
    ok( $checkpw, 'checkpw returns true with right password when logging in via cardnumber' );
Lines 792-804 subtest 'check_cookie_auth' => sub { Link Here
792
};
741
};
793
742
794
subtest 'checkauth & check_cookie_auth' => sub {
743
subtest 'checkauth & check_cookie_auth' => sub {
795
    plan tests => 35;
744
    plan tests => 34;
796
745
797
    # flags = 4 => { catalogue => 1 }
746
    # flags = 4 => { catalogue => 1 }
798
    my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 4 } });
747
    my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 4 } });
799
    my $password = 'password';
748
    my $password = set_weak_password($patron);
800
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
801
    $patron->set_password( { password => $password } );
802
749
803
    my $cgi_mock = Test::MockModule->new('CGI');
750
    my $cgi_mock = Test::MockModule->new('CGI');
804
    $cgi_mock->mock( 'request_method', sub { return 'POST' } );
751
    $cgi_mock->mock( 'request_method', sub { return 'POST' } );
Lines 825-840 subtest 'checkauth & check_cookie_auth' => sub { Link Here
825
    my $first_sessionID = $sessionID;
772
    my $first_sessionID = $sessionID;
826
773
827
    $ENV{"HTTP_COOKIE"} = "CGISESSID=$sessionID";
774
    $ENV{"HTTP_COOKIE"} = "CGISESSID=$sessionID";
828
    # Not authenticated yet, checkauth didn't return the session
775
    # Not authenticated yet, the login form is displayed
829
    {
776
    my $template;
830
        local *STDOUT;
777
    ( $userid, $cookie, $sessionID, $flags, $template ) = C4::Auth::checkauth($cgi, 0, {catalogue => 1}, 'intranet', undef, undef, { do_not_print => 1 } );
831
        my $stdout;
778
    is( $template->{VARS}->{loginprompt}, 1, );
832
        open STDOUT, '>', \$stdout;
833
        ( $userid, $cookie, $sessionID, $flags ) = C4::Auth::checkauth($cgi, 0, {catalogue => 1} );
834
        close STDOUT;
835
    }
836
    is( $sessionID, undef);
837
    is( $userid, undef);
838
779
839
    # Sending undefined fails obviously
780
    # Sending undefined fails obviously
840
    my ( $auth_status, $session ) = C4::Auth::check_cookie_auth($sessionID, {catalogue => 1} );
781
    my ( $auth_status, $session ) = C4::Auth::check_cookie_auth($sessionID, {catalogue => 1} );
Lines 874-886 subtest 'checkauth & check_cookie_auth' => sub { Link Here
874
    # Logging out!
815
    # Logging out!
875
    $cgi->param('logout.x', 1);
816
    $cgi->param('logout.x', 1);
876
    $cgi->delete( 'userid', 'password' );
817
    $cgi->delete( 'userid', 'password' );
877
    {
818
    ( $userid, $cookie, $sessionID, $flags, $template ) =
878
        local *STDOUT;
819
        C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
879
        my $stdout;
820
880
        open STDOUT, '>', \$stdout;
881
        ( $userid, $cookie, $sessionID, $flags ) = C4::Auth::checkauth($cgi, 0, {catalogue => 1});
882
        close STDOUT;
883
    }
884
    is( $sessionID, undef );
821
    is( $sessionID, undef );
885
    is( $ENV{"HTTP_COOKIE"}, "CGISESSID=$first_sessionID", 'HTTP_COOKIE not unset' );
822
    is( $ENV{"HTTP_COOKIE"}, "CGISESSID=$first_sessionID", 'HTTP_COOKIE not unset' );
886
    ( $auth_status, $session) = C4::Auth::check_cookie_auth( $first_sessionID, {catalogue => 1} );
823
    ( $auth_status, $session) = C4::Auth::check_cookie_auth( $first_sessionID, {catalogue => 1} );
Lines 1138-1145 subtest 'checkpw() return values tests' => sub { Link Here
1138
        $C4::Auth::ldap = 0;
1075
        $C4::Auth::ldap = 0;
1139
1076
1140
        my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
1077
        my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
1141
        my $password = 'thePassword123';
1078
        my $password = set_weak_password($patron);
1142
        $patron->set_password( { password => $password, skip_validation => 1 } );
1143
1079
1144
        my $patron_to_delete  = $builder->build_object( { class => 'Koha::Patrons' } );
1080
        my $patron_to_delete  = $builder->build_object( { class => 'Koha::Patrons' } );
1145
        my $unused_userid     = $patron_to_delete->userid;
1081
        my $unused_userid     = $patron_to_delete->userid;
Lines 1331-1333 subtest 'checkpw() return values tests' => sub { Link Here
1331
        $schema->storage->txn_rollback;
1267
        $schema->storage->txn_rollback;
1332
    };
1268
    };
1333
};
1269
};
1334
- 
1270
1271
sub set_weak_password {
1272
    my ($patron) = @_;
1273
    my $password = 'password';
1274
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
1275
    $patron->set_password( { password => $password } );
1276
    return $password;
1277
}

Return to bug 35904