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 1327-1334 sub checkauth { Link Here
1327
            $uri->query_param_delete('userid');
1328
            $uri->query_param_delete('userid');
1328
            $uri->query_param_delete('password');
1329
            $uri->query_param_delete('password');
1329
            $uri->query_param_delete('koha_login_context');
1330
            $uri->query_param_delete('koha_login_context');
1330
            print $query->redirect(-uri => $uri->as_string, -cookie => $cookie, -status=>'303 See other');
1331
            unless ( $params->{do_not_print} ) {
1331
            safe_exit;
1332
                print $query->redirect(-uri => $uri->as_string, -cookie => $cookie, -status=>'303 See other');
1333
                safe_exit;
1334
            }
1332
        }
1335
        }
1333
1336
1334
        return ( $userid, $cookie, $sessionID, $flags );
1337
        return ( $userid, $cookie, $sessionID, $flags );
Lines 1476-1483 sub checkauth { Link Here
1476
    );
1479
    );
1477
    $template->param(%info);
1480
    $template->param(%info);
1478
1481
1479
    #    $cookie = $query->cookie(CGISESSID => $session->id
1482
    if ( $params->{do_not_print} ) {
1480
    #   );
1483
        # This must be used for testing purpose only!
1484
        return ( undef, undef, undef, undef, $template );
1485
    }
1486
1481
    print $query->header(
1487
    print $query->header(
1482
        {   type              => 'text/html',
1488
        {   type              => 'text/html',
1483
            charset           => 'utf-8',
1489
            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 117-155 subtest 'checkauth() tests' => sub { Link Here
117
116
118
        my $password_expired;
117
        my $password_expired;
119
118
120
        my $patron_class = Test::MockModule->new('Koha::Patron');
119
        my $patron = $builder->build_object( { class => 'Koha::Patrons' } );
121
        $patron_class->mock( 'password_expired', sub { return $password_expired; } );
122
120
123
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 1 } });
121
        my $password = set_weak_password($patron);
124
        my $password = 'password';
122
        $patron->password_expiration_date( dt_from_string->subtract(days => 1) )->store;
125
        t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
126
        $patron->set_password( { password => $password } );
127
123
128
        my $cgi_mock = Test::MockModule->new('CGI')->mock( 'request_method', 'POST' );
124
        my $cgi_mock = Test::MockModule->new('CGI');
129
        my $cgi = CGI->new;
125
        $cgi_mock->mock( 'request_method', sub { return 'POST' } );
130
        $cgi->param( -name => 'userid',   -value => $patron->userid );
126
        my $cgi  = CGI->new;
131
        $cgi->param( -name => 'password', -value => $password );
127
        # Simulating the login form submission
128
        $cgi->param( 'userid',   $patron->userid );
129
        $cgi->param( 'password', $password );
132
130
133
        my $auth = Test::MockModule->new( 'C4::Auth' );
131
        my ( $userid, $cookie, $sessionID, $flags, $template ) = C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
134
        # Tests will fail if we hit safe_exit
132
        is( $template->{VARS}->{password_has_expired}, 1 );
135
        $auth->mock( 'safe_exit', sub { return } );
136
137
        my ( $userid, $cookie, $sessionID, $flags );
138
139
        {
140
            t::lib::Mocks::mock_preference( 'DumpTemplateVarsOpac', 1 );
141
            # checkauth will redirect and safe_exit if not authenticated and not authorized
142
            local *STDOUT;
143
            my $stdout;
144
            open STDOUT, '>', \$stdout;
145
146
            # Password has expired
147
            $password_expired = 1;
148
            C4::Auth::checkauth( $cgi, 0, { catalogue => 1 } );
149
            like( $stdout, qr{'password_has_expired' => 1}, 'password_has_expired is set to 1' );
150
151
            close STDOUT;
152
        };
153
    };
133
    };
154
134
155
    subtest 'Reset auth state when changing users' => sub {
135
    subtest 'Reset auth state when changing users' => sub {
Lines 173-180 subtest 'checkauth() tests' => sub { Link Here
173
            C4::Auth::check_cookie_auth( $sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
153
            C4::Auth::check_cookie_auth( $sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
174
        is( $return, 'ok', 'Patron authenticated' );
154
        is( $return, 'ok', 'Patron authenticated' );
175
155
176
        my $mock1 = Test::MockModule->new('C4::Auth');
177
        $mock1->mock( 'safe_exit', sub { return 'safe_exit_redirect' } );
178
        my $mock2 = Test::MockModule->new('CGI');
156
        my $mock2 = Test::MockModule->new('CGI');
179
        $mock2->mock( 'request_method', 'POST' );
157
        $mock2->mock( 'request_method', 'POST' );
180
        $mock2->mock( 'cookie',         sub { return $sessionID; } );    # oversimplified..
158
        $mock2->mock( 'cookie',         sub { return $sessionID; } );    # oversimplified..
Lines 183-203 subtest 'checkauth() tests' => sub { Link Here
183
        $cgi->param( -name => 'userid',             -value => 'Bond' );
161
        $cgi->param( -name => 'userid',             -value => 'Bond' );
184
        $cgi->param( -name => 'password',           -value => 'James Bond' );
162
        $cgi->param( -name => 'password',           -value => 'James Bond' );
185
        $cgi->param( -name => 'koha_login_context', -value => 1 );
163
        $cgi->param( -name => 'koha_login_context', -value => 1 );
186
        my ( @return, $stdout );
164
        my ( $userid, $cookie, $flags, $template );
187
        {
165
        ( $userid, $cookie, $sessionID, $flags, $template ) =
188
            local *STDOUT;
166
            C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
189
            local %ENV;
167
        is( $template->{VARS}->{loginprompt}, 1, 'Changing to non-existent user causes a redirect to login' );
190
            $ENV{REMOTE_ADDR} = '1.2.3.4';
191
            open STDOUT, '>', \$stdout;
192
            @return = C4::Auth::checkauth( $cgi, 0, {} );
193
            close STDOUT;
194
        }
195
        is( $return[0], 'safe_exit_redirect', 'Changing to non-existent user causes a redirect to login' );
196
    };
168
    };
197
169
198
199
    subtest 'While still logged in, relogin with another user' => sub {
170
    subtest 'While still logged in, relogin with another user' => sub {
200
        plan tests => 6;
171
        plan tests => 5;
201
172
202
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {} });
173
        my $patron = $builder->build_object({ class => 'Koha::Patrons', value => {} });
203
        my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => {} });
174
        my $patron2 = $builder->build_object({ class => 'Koha::Patrons', value => {} });
Lines 209-244 subtest 'checkauth() tests' => sub { Link Here
209
        $session->param( 'lasttime',     time() );
180
        $session->param( 'lasttime',     time() );
210
        $session->param( 'interface',    'opac' );
181
        $session->param( 'interface',    'opac' );
211
        $session->flush;
182
        $session->flush;
212
        my $sessionID = $session->id;
183
        my $previous_sessionID = $session->id;
213
        C4::Context->_new_userenv($sessionID);
184
        C4::Context->_new_userenv($previous_sessionID);
214
185
215
        my ( $return ) = C4::Auth::check_cookie_auth( $sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
186
        my ( $return ) = C4::Auth::check_cookie_auth( $previous_sessionID, undef, { skip_version_check => 1, remote_addr => '1.2.3.4' } );
216
        is( $return, 'ok', 'Former session in shape now' );
187
        is( $return, 'ok', 'Former session in shape now' );
217
188
218
        my $mock1 = Test::MockModule->new('C4::Auth');
189
        my $mock1 = Test::MockModule->new('C4::Auth');
219
        $mock1->mock( 'safe_exit', sub {} );
190
        $mock1->mock( 'safe_exit', sub {} );
220
        my $mock2 = Test::MockModule->new('CGI');
191
        my $mock2 = Test::MockModule->new('CGI');
221
        $mock2->mock( 'request_method', 'POST' );
192
        $mock2->mock( 'request_method', 'POST' );
222
        $mock2->mock( 'cookie', sub { return $sessionID; } ); # oversimplified..
193
        $mock2->mock( 'cookie', sub { return $previous_sessionID; } ); # oversimplified..
223
        my $cgi = CGI->new;
194
        my $cgi = CGI->new;
224
        my $password = 'Incr3d1blyZtr@ng93$';
195
        my $password = 'Incr3d1blyZtr@ng93$';
225
        $patron2->set_password({ password => $password });
196
        $patron2->set_password({ password => $password });
226
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
197
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
227
        $cgi->param( -name => 'password',           -value => $password );
198
        $cgi->param( -name => 'password',           -value => $password );
228
        $cgi->param( -name => 'koha_login_context', -value => 1 );
199
        $cgi->param( -name => 'koha_login_context', -value => 1 );
229
        my ( @return, $stdout );
200
        my ( $userid, $cookie, $sessionID, $flags, $template ) =
230
        {
201
            C4::Auth::checkauth( $cgi, 0, {}, 'opac', undef, undef, { do_not_print => 1 } );
231
            local *STDOUT;
202
        is( $userid, $patron2->userid, 'Login of patron2 approved' );
232
            local %ENV;
203
        isnt( $sessionID, $previous_sessionID, 'Did not return previous session ID' );
233
            $ENV{REMOTE_ADDR} = '1.2.3.4';
204
        ok( $sessionID, 'New session ID not empty' );
234
            open STDOUT, '>', \$stdout;
235
            @return = C4::Auth::checkauth( $cgi, 0, {} );
236
            close STDOUT;
237
        }
238
        # Note: We can test return values from checkauth here since we mocked the safe_exit after the Redirect 303
239
        is( $return[0], $patron2->userid, 'Login of patron2 approved' );
240
        isnt( $return[2], $sessionID, 'Did not return previous session ID' );
241
        ok( $return[2], 'New session ID not empty' );
242
205
243
        # Similar situation: Relogin with former session of $patron, new user $patron2 has no permissions
206
        # Similar situation: Relogin with former session of $patron, new user $patron2 has no permissions
244
        $patron2->flags(undef)->store;
207
        $patron2->flags(undef)->store;
Lines 246-267 subtest 'checkauth() tests' => sub { Link Here
246
        $session->param( 'id',           $patron->userid );
209
        $session->param( 'id',           $patron->userid );
247
        $session->param( 'interface',    'intranet' );
210
        $session->param( 'interface',    'intranet' );
248
        $session->flush;
211
        $session->flush;
249
        $sessionID = $session->id;
212
        $previous_sessionID = $session->id;
250
        C4::Context->_new_userenv($sessionID);
213
        C4::Context->_new_userenv($previous_sessionID);
251
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
214
        $cgi->param( -name => 'userid',             -value => $patron2->userid );
252
        $cgi->param( -name => 'password',           -value => $password );
215
        $cgi->param( -name => 'password',           -value => $password );
253
        $cgi->param( -name => 'koha_login_context', -value => 1 );
216
        $cgi->param( -name => 'koha_login_context', -value => 1 );
254
        {
217
        ( $userid, $cookie, $sessionID, $flags, $template ) =
255
            local *STDOUT;
218
            C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
256
            local %ENV;
219
        is( $template->{VARS}->{nopermission}, 1, 'No permission response' );
257
            $ENV{REMOTE_ADDR} = '1.2.3.4';
258
            $stdout = q{};
259
            open STDOUT, '>', \$stdout;
260
            @return = C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet' ); # patron2 has no catalogue perm
261
            close STDOUT;
262
        }
263
        like( $stdout, qr/You do not have permission to access this page/, 'No permission response' );
264
        is( @return, 0, 'checkauth returned failure' );
265
    };
220
    };
266
221
267
    subtest 'Two-factor authentication' => sub {
222
    subtest 'Two-factor authentication' => sub {
Lines 380-388 subtest 'checkauth() tests' => sub { Link Here
380
335
381
        my $branch = $builder->build_object({ class => 'Koha::Libraries' });
336
        my $branch = $builder->build_object({ class => 'Koha::Libraries' });
382
337
383
        my $password = 'password';
338
        my $password = set_weak_password($staff_user);
384
        t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
385
        $staff_user->set_password( { password => $password } );
386
        my $cgi = Test::MockObject->new();
339
        my $cgi = Test::MockObject->new();
387
        $cgi->mock( 'cookie', sub { return; } );
340
        $cgi->mock( 'cookie', sub { return; } );
388
        $cgi->mock(
341
        $cgi->mock(
Lines 421-430 subtest 'no_set_userenv parameter tests' => sub { Link Here
421
374
422
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
375
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
423
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
376
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
424
    my $password = 'password';
425
377
426
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
378
    my $password = set_weak_password($patron);
427
    $patron->set_password({ password => $password });
428
379
429
    ok( checkpw( $patron->userid, $password, undef, undef, 1 ), 'checkpw returns true' );
380
    ok( checkpw( $patron->userid, $password, undef, undef, 1 ), 'checkpw returns true' );
430
    is( C4::Context->userenv, undef, 'Userenv should be undef as required' );
381
    is( C4::Context->userenv, undef, 'Userenv should be undef as required' );
Lines 443-452 subtest 'checkpw lockout tests' => sub { Link Here
443
394
444
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
395
    my $library = $builder->build_object( { class => 'Koha::Libraries' } );
445
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
396
    my $patron  = $builder->build_object( { class => 'Koha::Patrons' } );
446
    my $password = 'password';
397
    my $password = set_weak_password($patron);
447
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
448
    t::lib::Mocks::mock_preference( 'FailedLoginAttempts', 1 );
398
    t::lib::Mocks::mock_preference( 'FailedLoginAttempts', 1 );
449
    $patron->set_password({ password => $password });
450
399
451
    my ( $checkpw, undef, undef ) = checkpw( $patron->cardnumber, $password, undef, undef, 1 );
400
    my ( $checkpw, undef, undef ) = checkpw( $patron->cardnumber, $password, undef, undef, 1 );
452
    ok( $checkpw, 'checkpw returns true with right password when logging in via cardnumber' );
401
    ok( $checkpw, 'checkpw returns true with right password when logging in via cardnumber' );
Lines 764-776 subtest 'check_cookie_auth' => sub { Link Here
764
};
713
};
765
714
766
subtest 'checkauth & check_cookie_auth' => sub {
715
subtest 'checkauth & check_cookie_auth' => sub {
767
    plan tests => 35;
716
    plan tests => 34;
768
717
769
    # flags = 4 => { catalogue => 1 }
718
    # flags = 4 => { catalogue => 1 }
770
    my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 4 } });
719
    my $patron = $builder->build_object({ class => 'Koha::Patrons', value => { flags => 4 } });
771
    my $password = 'password';
720
    my $password = set_weak_password($patron);
772
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
773
    $patron->set_password( { password => $password } );
774
721
775
    my $cgi_mock = Test::MockModule->new('CGI');
722
    my $cgi_mock = Test::MockModule->new('CGI');
776
    $cgi_mock->mock( 'request_method', sub { return 'POST' } );
723
    $cgi_mock->mock( 'request_method', sub { return 'POST' } );
Lines 797-812 subtest 'checkauth & check_cookie_auth' => sub { Link Here
797
    my $first_sessionID = $sessionID;
744
    my $first_sessionID = $sessionID;
798
745
799
    $ENV{"HTTP_COOKIE"} = "CGISESSID=$sessionID";
746
    $ENV{"HTTP_COOKIE"} = "CGISESSID=$sessionID";
800
    # Not authenticated yet, checkauth didn't return the session
747
    # Not authenticated yet, the login form is displayed
801
    {
748
    my $template;
802
        local *STDOUT;
749
    ( $userid, $cookie, $sessionID, $flags, $template ) = C4::Auth::checkauth($cgi, 0, {catalogue => 1}, 'intranet', undef, undef, { do_not_print => 1 } );
803
        my $stdout;
750
    is( $template->{VARS}->{loginprompt}, 1, );
804
        open STDOUT, '>', \$stdout;
805
        ( $userid, $cookie, $sessionID, $flags ) = C4::Auth::checkauth($cgi, 0, {catalogue => 1} );
806
        close STDOUT;
807
    }
808
    is( $sessionID, undef);
809
    is( $userid, undef);
810
751
811
    # Sending undefined fails obviously
752
    # Sending undefined fails obviously
812
    my ( $auth_status, $session ) = C4::Auth::check_cookie_auth($sessionID, {catalogue => 1} );
753
    my ( $auth_status, $session ) = C4::Auth::check_cookie_auth($sessionID, {catalogue => 1} );
Lines 846-858 subtest 'checkauth & check_cookie_auth' => sub { Link Here
846
    # Logging out!
787
    # Logging out!
847
    $cgi->param('logout.x', 1);
788
    $cgi->param('logout.x', 1);
848
    $cgi->delete( 'userid', 'password' );
789
    $cgi->delete( 'userid', 'password' );
849
    {
790
    ( $userid, $cookie, $sessionID, $flags, $template ) =
850
        local *STDOUT;
791
        C4::Auth::checkauth( $cgi, 0, { catalogue => 1 }, 'intranet', undef, undef, { do_not_print => 1 } );
851
        my $stdout;
792
852
        open STDOUT, '>', \$stdout;
853
        ( $userid, $cookie, $sessionID, $flags ) = C4::Auth::checkauth($cgi, 0, {catalogue => 1});
854
        close STDOUT;
855
    }
856
    is( $sessionID, undef );
793
    is( $sessionID, undef );
857
    is( $ENV{"HTTP_COOKIE"}, "CGISESSID=$first_sessionID", 'HTTP_COOKIE not unset' );
794
    is( $ENV{"HTTP_COOKIE"}, "CGISESSID=$first_sessionID", 'HTTP_COOKIE not unset' );
858
    ( $auth_status, $session) = C4::Auth::check_cookie_auth( $first_sessionID, {catalogue => 1} );
795
    ( $auth_status, $session) = C4::Auth::check_cookie_auth( $first_sessionID, {catalogue => 1} );
Lines 1110-1117 subtest 'checkpw() return values tests' => sub { Link Here
1110
        $C4::Auth::ldap = 0;
1047
        $C4::Auth::ldap = 0;
1111
1048
1112
        my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
1049
        my $patron   = $builder->build_object( { class => 'Koha::Patrons' } );
1113
        my $password = 'thePassword123';
1050
        my $password = set_weak_password($patron);
1114
        $patron->set_password( { password => $password, skip_validation => 1 } );
1115
1051
1116
        my $patron_to_delete  = $builder->build_object( { class => 'Koha::Patrons' } );
1052
        my $patron_to_delete  = $builder->build_object( { class => 'Koha::Patrons' } );
1117
        my $unused_userid     = $patron_to_delete->userid;
1053
        my $unused_userid     = $patron_to_delete->userid;
Lines 1303-1305 subtest 'checkpw() return values tests' => sub { Link Here
1303
        $schema->storage->txn_rollback;
1239
        $schema->storage->txn_rollback;
1304
    };
1240
    };
1305
};
1241
};
1306
- 
1242
1243
sub set_weak_password {
1244
    my ($patron) = @_;
1245
    my $password = 'password';
1246
    t::lib::Mocks::mock_preference( 'RequireStrongPassword', 0 );
1247
    $patron->set_password( { password => $password } );
1248
    return $password;
1249
}

Return to bug 35904