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

(-)a/Koha/Patron.pm (+27 lines)
Lines 24-29 use Carp; Link Here
24
24
25
use C4::Context;
25
use C4::Context;
26
use C4::Log;
26
use C4::Log;
27
use Koha::AuthUtils;
27
use Koha::Database;
28
use Koha::Database;
28
use Koha::DateUtils;
29
use Koha::DateUtils;
29
use Koha::Issues;
30
use Koha::Issues;
Lines 267-272 sub track_login { Link Here
267
    $self->lastseen( dt_from_string() )->store;
268
    $self->lastseen( dt_from_string() )->store;
268
}
269
}
269
270
271
=head2 change_password_to
272
273
my $changed = $patron->change_password_to($cleartext_password);
274
275
Changes patron's password to C<$cleartext_password>. This subroutine
276
also makes validations for new password, but does not check the old
277
one.
278
279
=cut
280
281
sub change_password_to {
282
    my ($self, $cleartext_password) = @_;
283
284
    my $min_length = C4::Context->preference("minPasswordLength");
285
    if ($min_length > length($cleartext_password)) {
286
        return (undef, "Password is too short. Minimum length: $min_length.");
287
    }
288
    if ($cleartext_password =~ m|^\s+| or $cleartext_password =~ m|\s+$|) {
289
        return (undef, "Password cannot contain trailing whitespaces.");
290
    }
291
    my $hashed_password = Koha::AuthUtils::hash_password($cleartext_password);
292
    $self->set({ password => $hashed_password })->store;
293
    logaction( "MEMBERS", "CHANGE PASS", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
294
    return 1;
295
}
296
270
=head3 type
297
=head3 type
271
298
272
=cut
299
=cut
(-)a/Koha/REST/V1/Patron.pm (+22 lines)
Lines 19-24 use Modern::Perl; Link Here
19
19
20
use Mojo::Base 'Mojolicious::Controller';
20
use Mojo::Base 'Mojolicious::Controller';
21
21
22
23
use C4::Auth qw( haspermission checkpw_internal );
22
use Koha::Patrons;
24
use Koha::Patrons;
23
25
24
sub list {
26
sub list {
Lines 44-47 sub get { Link Here
44
    return $c->$cb($patron->unblessed, 200);
46
    return $c->$cb($patron->unblessed, 200);
45
}
47
}
46
48
49
sub changepassword {
50
    my ($c, $args, $cb) = @_;
51
52
    my $user = $c->stash('koha.user');
53
    my $patron = Koha::Patrons->find($args->{borrowernumber});
54
    return $c->$cb({ error => "Patron not found." }, 404) unless $patron;
55
56
    my $pw = $args->{'body'};
57
    my $dbh = C4::Context->dbh;
58
    unless (checkpw_internal($dbh, $user->userid, $pw->{'current_password'})) {
59
        return $c->$cb({ error => "Wrong current password." }, 400);
60
    }
61
62
    my ($success, $errmsg) = $user->change_password_to($pw->{'new_password'});
63
    if ($errmsg) {
64
        return $c->$cb({ error => $errmsg }, 400);
65
    }
66
    return $c->$cb({}, 200);
67
}
68
47
1;
69
1;
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 10-14 Link Here
10
  },
10
  },
11
  "/patrons/{borrowernumber}": {
11
  "/patrons/{borrowernumber}": {
12
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
12
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
13
  },
14
  "/patrons/{borrowernumber}/password": {
15
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}~1password"
13
  }
16
  }
14
}
17
}
(-)a/api/v1/swagger/paths/patrons.json (+60 lines)
Lines 69-73 Link Here
69
        }
69
        }
70
      }
70
      }
71
    }
71
    }
72
  },
73
  "/patrons/{borrowernumber}/password": {
74
    "patch": {
75
      "operationId": "changepasswordPatron",
76
      "tags": ["patrons"],
77
      "parameters": [{
78
          "$ref": "../parameters.json#/borrowernumberPathParam"
79
        }, {
80
          "name": "body",
81
          "in": "body",
82
          "description": "A JSON object containing informations about passwords",
83
          "required": true,
84
          "schema": {
85
            "type": "object",
86
            "properties": {
87
              "current_password": {
88
                "description": "Current password",
89
                "type": "string"
90
              },
91
              "new_password": {
92
                "description": "New password",
93
                "type": "string"
94
              }
95
            }
96
          }
97
        }
98
      ],
99
      "produces": [
100
          "application/json"
101
      ],
102
      "responses": {
103
        "200": {
104
          "description": "Password changed"
105
        },
106
        "400": {
107
          "description": "Bad request",
108
          "schema": {
109
            "$ref": "../definitions.json#/error"
110
          }
111
        },
112
        "403": {
113
          "description": "Access forbidden",
114
          "schema": {
115
            "$ref": "../definitions.json#/error"
116
          }
117
        },
118
        "404": {
119
          "description": "Patron not found",
120
          "schema": {
121
            "$ref": "../definitions.json#/error"
122
          }
123
        }
124
      },
125
      "x-koha-authorization": {
126
        "allow-owner": true,
127
        "permissions": {
128
          "borrowers": "1"
129
        }
130
      }
131
    }
72
  }
132
  }
73
}
133
}
(-)a/t/db_dependent/api/v1/patrons.t (-7 / +60 lines)
Lines 17-43 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 20;
20
use Test::More tests => 38;
21
use Test::Mojo;
21
use Test::Mojo;
22
use t::lib::TestBuilder;
22
use t::lib::TestBuilder;
23
use t::lib::Mocks;
23
24
24
use C4::Auth;
25
use C4::Auth;
25
use C4::Context;
26
use C4::Context;
26
27
28
use Koha::AuthUtils;
27
use Koha::Database;
29
use Koha::Database;
28
use Koha::Patron;
30
use Koha::Patron;
29
31
30
my $builder = t::lib::TestBuilder->new();
32
my $builder = t::lib::TestBuilder->new();
31
33
32
my $dbh = C4::Context->dbh;
34
my $schema = Koha::Database->new->schema;
33
$dbh->{AutoCommit} = 0;
35
$schema->storage->txn_begin;
34
$dbh->{RaiseError} = 1;
35
36
36
$ENV{REMOTE_ADDR} = '127.0.0.1';
37
$ENV{REMOTE_ADDR} = '127.0.0.1';
37
my $t = Test::Mojo->new('Koha::REST::V1');
38
my $t = Test::Mojo->new('Koha::REST::V1');
38
39
39
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
40
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
40
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
41
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
42
41
my $guarantor = $builder->build({
43
my $guarantor = $builder->build({
42
    source => 'Borrower',
44
    source => 'Borrower',
43
    value => {
45
    value => {
Lines 46-51 my $guarantor = $builder->build({ Link Here
46
        flags        => 0,
48
        flags        => 0,
47
    }
49
    }
48
});
50
});
51
52
my $password = "secret";
49
my $borrower = $builder->build({
53
my $borrower = $builder->build({
50
    source => 'Borrower',
54
    source => 'Borrower',
51
    value => {
55
    value => {
Lines 81-86 $tx->req->cookies({name => 'CGISESSID', value => $session->id}); Link Here
81
$t->request_ok($tx)
85
$t->request_ok($tx)
82
  ->status_is(403);
86
  ->status_is(403);
83
87
88
84
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . ($borrower->{ borrowernumber }-1));
89
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . ($borrower->{ borrowernumber }-1));
85
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
90
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
86
$t->request_ok($tx)
91
$t->request_ok($tx)
Lines 100-111 $t->request_ok($tx) Link Here
100
  ->status_is(200)
105
  ->status_is(200)
101
  ->json_is('/guarantorid', $guarantor->{borrowernumber});
106
  ->json_is('/guarantorid', $guarantor->{borrowernumber});
102
107
108
my $password_obj = {
109
    current_password    => $password,
110
    new_password        => "new password",
111
};
112
113
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/-100/password' => json => $password_obj);
114
$t->request_ok($tx)
115
  ->status_is(401);
116
117
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$borrower->{borrowernumber}.'/password');
118
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
119
$t->request_ok($tx)
120
  ->status_is(400);
121
122
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$guarantor->{borrowernumber}.'/password' => json => $password_obj);
123
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
124
$t->request_ok($tx)
125
  ->status_is(403);
126
103
my $loggedinuser = $builder->build({
127
my $loggedinuser = $builder->build({
104
    source => 'Borrower',
128
    source => 'Borrower',
105
    value => {
129
    value => {
106
        branchcode   => $branchcode,
130
        branchcode   => $branchcode,
107
        categorycode => $categorycode,
131
        categorycode => $categorycode,
108
        flags        => 16 # borrowers flag
132
        flags        => 16, # borrowers flag
133
        password     => Koha::AuthUtils::hash_password($password),
109
    }
134
    }
110
});
135
});
111
136
Lines 129-132 $t->request_ok($tx) Link Here
129
  ->json_is('/borrowernumber' => $borrower->{ borrowernumber })
154
  ->json_is('/borrowernumber' => $borrower->{ borrowernumber })
130
  ->json_is('/surname' => $borrower->{ surname });
155
  ->json_is('/surname' => $borrower->{ surname });
131
156
132
$dbh->rollback;
157
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/-100/password' => json => $password_obj);
158
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
159
$t->request_ok($tx)
160
  ->status_is(404);
161
162
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
163
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
164
$t->request_ok($tx)
165
  ->status_is(200);
166
167
ok(C4::Auth::checkpw_hash($password_obj->{'new_password'}, Koha::Patrons->find($loggedinuser->{borrowernumber})->password), "New password in database.");
168
is(C4::Auth::checkpw_hash($password_obj->{'current_password'}, Koha::Patrons->find($loggedinuser->{borrowernumber})->password), "", "Old password is gone.");
169
170
$password_obj->{'current_password'} = $password_obj->{'new_password'};
171
$password_obj->{'new_password'} = "a";
172
t::lib::Mocks::mock_preference("minPasswordLength", 5);
173
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
174
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
175
$t->request_ok($tx)
176
  ->status_is(400)
177
  ->json_like('/error', qr/Password is too short/, "Password too short");
178
179
$password_obj->{'new_password'} = " abcdef ";
180
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
181
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
182
$t->request_ok($tx)
183
  ->status_is(400)
184
  ->json_is('/error', "Password cannot contain trailing whitespaces.");
185
186
$schema->storage->txn_rollback;
133
- 

Return to bug 17006