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

(-)a/Koha/REST/V1/Auth/Password.pm (-2 / +2 lines)
Lines 41-48 Controller method that checks a patron's password Link Here
41
sub validate {
41
sub validate {
42
    my $c = shift->openapi->valid_input or return;
42
    my $c = shift->openapi->valid_input or return;
43
    my $body   = $c->validation->param('body');
43
    my $body   = $c->validation->param('body');
44
    my $username = $body->{username} // '';
44
    my $userid = $body->{userid} // '';
45
    my $patron = Koha::Patrons->find({ userid => $username });
45
    my $patron = Koha::Patrons->find({ userid => $userid });
46
46
47
    unless ($patron) {
47
    unless ($patron) {
48
        return $c->render( status => 400, openapi => { error => "Validation failed" } );
48
        return $c->render( status => 400, openapi => { error => "Validation failed" } );
(-)a/api/v1/swagger/paths/auth.yaml (-2 / +2 lines)
Lines 1070-1083 Link Here
1070
        schema:
1070
        schema:
1071
          type: object
1071
          type: object
1072
          properties:
1072
          properties:
1073
            username:
1073
            userid:
1074
              description: Username
1074
              description: Username
1075
              type: string
1075
              type: string
1076
            password:
1076
            password:
1077
              description: Password (plain text)
1077
              description: Password (plain text)
1078
              type: string
1078
              type: string
1079
          required:
1079
          required:
1080
            - username
1080
            - userid
1081
            - password
1081
            - password
1082
          additionalProperties: false
1082
          additionalProperties: false
1083
    produces:
1083
    produces:
(-)a/t/db_dependent/api/v1/password_validation.t (-149 / +64 lines)
Lines 18-208 Link Here
18
18
19
use Modern::Perl;
19
use Modern::Perl;
20
20
21
use Test::More tests => 7;
21
use Test::More tests => 6;
22
use Test::Mojo;
22
use Test::Mojo;
23
use Test::Warn;
24
use Mojo::JWT;
25
use Crypt::OpenSSL::RSA;
26
23
27
use t::lib::TestBuilder;
24
use t::lib::TestBuilder;
28
use t::lib::Mocks;
25
use t::lib::Mocks;
29
26
30
use Koha::Database;
27
use Koha::Database;
31
use Koha::AuthUtils;
32
use C4::Auth;
33
use Data::Dumper;
34
28
35
my $schema  = Koha::Database->new->schema;
29
my $schema  = Koha::Database->new->schema;
36
my $builder = t::lib::TestBuilder->new;
30
my $builder = t::lib::TestBuilder->new;
37
31
38
# FIXME: sessionStorage defaults to mysql, but it seems to break transaction handling
32
my $t = Test::Mojo->new('Koha::REST::V1');
39
# this affects the other REST api tests
33
t::lib::Mocks::mock_preference( 'RESTBasicAuth', 1 );
40
t::lib::Mocks::mock_preference( 'SessionStorage', 'tmp' );
41
34
42
my $remote_address = '127.0.0.1';
35
$schema->storage->txn_begin;
36
37
# create a privileged user
38
my $librarian = $builder->build_object(
39
    {
40
        class => 'Koha::Patrons',
41
        value => { flags => 2 ** 4 } # borrowers flag = 4
42
    }
43
);
44
my $password = 'thePassword123';
45
$librarian->set_password( { password => $password, skip_validation => 1 } );
46
my $userid = $librarian->userid;
43
47
44
subtest 'password validation - success' => sub {
48
subtest 'password validation - success' => sub {
49
45
    plan tests => 3;
50
    plan tests => 3;
46
51
47
    $schema->storage->txn_begin;
52
    $schema->storage->txn_begin;
48
53
49
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 1 } );
50
    my $patron = Koha::Patrons->find($borrowernumber);
51
    my $userid = $patron->userid;
52
53
    my $t = Test::Mojo->new('Koha::REST::V1');
54
55
    my $json = {
54
    my $json = {
56
        "username" => $userid,
55
        "userid"   => $userid,
57
        "password" => "test",
56
        "password" => $password,
58
    };
57
    };
59
58
60
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => $json );
59
    $t->post_ok( "//$userid:$password@/api/v1/auth/password/validation" => json => $json )
61
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
60
      ->status_is(204)
62
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
61
      ->content_is(q{});
63
64
    my $resp = $t->request_ok($tx);
65
    $resp->content_is('');
66
    $resp->status_is(204);
67
62
68
    $schema->storage->txn_rollback;
63
    $schema->storage->txn_rollback;
69
};
64
};
70
65
71
subtest 'password validation - account lock out' => sub {
66
subtest 'password validation - account lock out' => sub {
67
72
    plan tests => 6;
68
    plan tests => 6;
73
69
74
    $schema->storage->txn_begin;
70
    $schema->storage->txn_begin;
75
71
76
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 1 } );
72
    t::lib::Mocks::mock_preference( 'FailedLoginAttempts', 1 );
77
    my $patron = Koha::Patrons->find($borrowernumber);
78
    my $userid = $patron->userid;
79
73
80
    my $t = Test::Mojo->new('Koha::REST::V1');
74
    my $json = {
75
        "userid"   => $userid,
76
        "password" => "bad",
77
    };
81
78
82
    t::lib::Mocks::mock_preference( 'FailedLoginAttempts', 1 );
79
    $t->post_ok( "//$userid:$password@/api/v1/auth/password/validation" => json => $json )
80
      ->status_is(400)
81
      ->json_is({ error => q{Validation failed} });
83
82
84
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => { "username" => $userid, "password" => "bad"} );
83
    $json->{password} = $password;
85
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
86
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
87
    my $resp = $t->request_ok($tx);
88
    $resp->json_is('/error' => 'Validation failed');
89
    $resp->status_is(400);
90
84
91
    my $tx2 = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => { "username" => $userid, "password" => "test"} );
85
    $t->post_ok( "//$userid:$password@/api/v1/auth/password/validation" => json => $json )
92
    $tx2->req->cookies( { name => 'CGISESSID', value => $session_id } );
86
      ->status_is(400)
93
    $tx2->req->env( { REMOTE_ADDR => $remote_address } );
87
      ->json_is({ error => q{Validation failed} });
94
    my $resp2 = $t->request_ok($tx2);
95
    $resp2->json_is('/error' => 'Validation failed');
96
    $resp2->status_is(400);
97
88
98
    $schema->storage->txn_rollback;
89
    $schema->storage->txn_rollback;
99
};
90
};
100
91
101
92
102
subtest 'password validation - bad username' => sub {
93
subtest 'password validation - bad userid' => sub {
94
103
    plan tests => 3;
95
    plan tests => 3;
104
96
105
    $schema->storage->txn_begin;
97
    $schema->storage->txn_begin;
106
98
107
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 1 } );
108
    my $patron = Koha::Patrons->find($borrowernumber);
109
    my $userid = $patron->userid;
110
111
    my $t = Test::Mojo->new('Koha::REST::V1');
112
113
    my $json = {
99
    my $json = {
114
        "username" => '1234567890abcdefghijklmnopqrstuvwxyz@koha-community.org',
100
        "userid"   => '1234567890abcdefghijklmnopqrstuvwxyz@koha-community.org',
115
        "password" => "test",
101
        "password" => $password,
116
    };
102
    };
117
103
118
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => $json );
104
    $t->post_ok( "//$userid:$password@/api/v1/auth/password/validation" => json => $json )
119
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
105
      ->status_is(400)
120
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
106
      ->json_is({ error => q{Validation failed} });
121
122
    my $resp = $t->request_ok($tx);
123
    $resp->json_is('/error' => 'Validation failed');
124
    $resp->status_is(400);
125
107
126
    $schema->storage->txn_rollback;
108
    $schema->storage->txn_rollback;
127
};
109
};
128
110
129
subtest 'password validation - bad password' => sub {
111
subtest 'password validation - bad password' => sub {
130
    plan tests => 3;
131
132
    $schema->storage->txn_begin;
133
134
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 1 } );
135
    my $patron = Koha::Patrons->find($borrowernumber);
136
    my $userid = $patron->userid;
137
138
    my $t = Test::Mojo->new('Koha::REST::V1');
139
140
    my $json = {
141
        "username" => $userid,
142
        "password" => "bad",
143
    };
144
145
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => $json );
146
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
147
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
148
149
    my $resp = $t->request_ok($tx);
150
    $resp->json_is('/error' => 'Validation failed');
151
    $resp->status_is(400);
152
153
    $schema->storage->txn_rollback;
154
};
155
112
156
subtest 'password validation - syntax error in payload' => sub {
157
    plan tests => 3;
113
    plan tests => 3;
158
114
159
    $schema->storage->txn_begin;
115
    $schema->storage->txn_begin;
160
116
161
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 1 } );
162
    my $patron = Koha::Patrons->find($borrowernumber);
163
    my $userid = $patron->userid;
164
165
    my $t = Test::Mojo->new('Koha::REST::V1');
166
167
    my $json = {
117
    my $json = {
168
        "username" => $userid,
118
        "userid"   => $userid,
169
        "password2" => "test",
119
        "password" => 'bad',
170
    };
120
    };
171
121
172
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => $json );
122
    $t->post_ok( "//$userid:$password@/api/v1/auth/password/validation" => json => $json )
173
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
123
      ->status_is(400)
174
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
124
      ->json_is({ error => q{Validation failed} });
175
176
    my $resp = $t->request_ok($tx);
177
    $resp->json_is('' => {"errors" => [{"message" => "Properties not allowed: password2.","path" => "\/body"}],"status" => 400} );
178
    $resp->status_is(400);
179
125
180
    $schema->storage->txn_rollback;
126
    $schema->storage->txn_rollback;
181
};
127
};
182
128
183
subtest 'password validation - unauthorized user' => sub {
129
subtest 'password validation - unauthorized user' => sub {
130
184
    plan tests => 3;
131
    plan tests => 3;
185
132
186
    $schema->storage->txn_begin;
133
    $schema->storage->txn_begin;
187
134
188
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 0 } );
135
    my $patron = $builder->build_object(
189
    my $patron = Koha::Patrons->find($borrowernumber);
136
        {
137
            class => 'Koha::Patrons',
138
            value => { flags => 2 ** 2 } # catalogue flag = 2
139
        }
140
    );
141
    my $password = 'thePassword123';
142
    $patron->set_password( { password => $password, skip_validation => 1 } );
190
    my $userid = $patron->userid;
143
    my $userid = $patron->userid;
191
144
192
    my $t = Test::Mojo->new('Koha::REST::V1');
193
194
    my $json = {
145
    my $json = {
195
        "username" => $userid,
146
        "userid"   => $userid,
196
        "password" => "test",
147
        "password" => "test",
197
    };
148
    };
198
149
199
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => $json );
150
    $t->post_ok( "//$userid:$password@/api/v1/auth/password/validation" => json => $json )
200
    $tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
151
      ->status_is(403)
201
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
152
      ->json_is('/error' => 'Authorization failure. Missing required permission(s).');
202
203
    my $resp = $t->request_ok($tx);
204
    $resp->json_is('/error' => 'Authorization failure. Missing required permission(s).');
205
    $resp->status_is(403);
206
153
207
    $schema->storage->txn_rollback;
154
    $schema->storage->txn_rollback;
208
};
155
};
Lines 212-258 subtest 'password validation - unauthenticated user' => sub { Link Here
212
159
213
    $schema->storage->txn_begin;
160
    $schema->storage->txn_begin;
214
161
215
    my ( $borrowernumber, $session_id ) = create_user_and_session( { authorized => 0 } );
216
    my $patron = Koha::Patrons->find($borrowernumber);
217
    my $userid = $patron->userid;
218
219
    my $t = Test::Mojo->new('Koha::REST::V1');
220
221
    my $json = {
162
    my $json = {
222
        "username" => $userid,
163
        "userid"   => "banana",
223
        "password" => "test",
164
        "password" => "test",
224
    };
165
    };
225
166
226
    my $tx = $t->ua->build_tx( POST => "/api/v1/auth/password/validation", json => $json );
167
    $t->post_ok( "/api/v1/auth/password/validation" => json => $json )
227
    #$tx->req->cookies( { name => 'CGISESSID', value => $session_id } );
168
      ->json_is( '/error' => 'Authentication failure.' )
228
    $tx->req->env( { REMOTE_ADDR => $remote_address } );
169
      ->status_is(401);
229
230
    my $resp = $t->request_ok($tx);
231
    $resp->json_is('/error' => 'Authentication failure.');
232
    $resp->status_is(401);
233
170
234
    $schema->storage->txn_rollback;
171
    $schema->storage->txn_rollback;
235
};
172
};
236
173
237
sub create_user_and_session {
174
$schema->storage->txn_rollback;
238
239
    my $args  = shift;
240
    my $flags = ( $args->{authorized} ) ? 1 : 0;
241
242
    my $password = Koha::AuthUtils::hash_password('test');
243
    my $user = $builder->build(
244
        {   source => 'Borrower',
245
            value  => { flags => $flags, password => $password }
246
        }
247
    );
248
249
    # Create a session for the authorized user
250
    my $session = C4::Auth::get_session('');
251
    $session->param( 'number',   $user->{borrowernumber} );
252
    $session->param( 'id',       $user->{userid} );
253
    $session->param( 'ip',       $remote_address );
254
    $session->param( 'lasttime', time() );
255
    $session->flush;
256
257
    return ( $user->{borrowernumber}, $session->id );
258
}
259
- 

Return to bug 30962