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::Checkouts;
28
use Koha::Checkouts;
28
use Koha::Database;
29
use Koha::Database;
29
use Koha::DateUtils;
30
use Koha::DateUtils;
Lines 709-714 sub account_locked { Link Here
709
          and $self->login_attempts >= $FailedLoginAttempts )? 1 : 0;
710
          and $self->login_attempts >= $FailedLoginAttempts )? 1 : 0;
710
}
711
}
711
712
713
=head3 change_password_to
714
715
my $changed = $patron->change_password_to($cleartext_password);
716
717
Changes patron's password to C<$cleartext_password>. This subroutine
718
also makes validations for new password, but does not check the old
719
one.
720
721
=cut
722
723
sub change_password_to {
724
    my ($self, $cleartext_password) = @_;
725
726
    my $min_length = C4::Context->preference("minPasswordLength");
727
    if ($min_length > length($cleartext_password)) {
728
        return (undef, "Password is too short. Minimum length: $min_length.");
729
    }
730
    if ($cleartext_password =~ m|^\s+| or $cleartext_password =~ m|\s+$|) {
731
        return (undef, "Password cannot contain trailing whitespaces.");
732
    }
733
    my $hashed_password = Koha::AuthUtils::hash_password($cleartext_password);
734
    $self->set({ password => $hashed_password })->store;
735
    logaction( "MEMBERS", "CHANGE PASS", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
736
    return 1;
737
}
738
712
=head3 type
739
=head3 type
713
740
714
=cut
741
=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 41-44 sub get { Link Here
41
    return $c->render(status => 200, openapi => $patron);
43
    return $c->render(status => 200, openapi => $patron);
42
}
44
}
43
45
46
sub changepassword {
47
    my ($c, $args, $cb) = @_;
48
49
    my $user = $c->stash('koha.user');
50
    my $patron = Koha::Patrons->find($args->{borrowernumber});
51
    return $c->$cb({ error => "Patron not found." }, 404) unless $patron;
52
53
    my $pw = $args->{'body'};
54
    my $dbh = C4::Context->dbh;
55
    unless (checkpw_internal($dbh, $user->userid, $pw->{'current_password'})) {
56
        return $c->$cb({ error => "Wrong current password." }, 400);
57
    }
58
59
    my ($success, $errmsg) = $user->change_password_to($pw->{'new_password'});
60
    if ($errmsg) {
61
        return $c->$cb({ error => $errmsg }, 400);
62
    }
63
    return $c->$cb({}, 200);
64
}
65
44
1;
66
1;
(-)a/api/v1/swagger/paths.json (+3 lines)
Lines 23-28 Link Here
23
  "/patrons/{borrowernumber}": {
23
  "/patrons/{borrowernumber}": {
24
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
24
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
25
  },
25
  },
26
  "/patrons/{borrowernumber}/password": {
27
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}~1password"
28
  },
26
  "/illrequests": {
29
  "/illrequests": {
27
    "$ref": "paths/illrequests.json#/~1illrequests"
30
    "$ref": "paths/illrequests.json#/~1illrequests"
28
  }
31
  }
(-)a/api/v1/swagger/paths/patrons.json (+60 lines)
Lines 107-111 Link Here
107
        }
107
        }
108
      }
108
      }
109
    }
109
    }
110
  },
111
  "/patrons/{borrowernumber}/password": {
112
    "patch": {
113
      "operationId": "changepasswordPatron",
114
      "tags": ["patrons"],
115
      "parameters": [{
116
          "$ref": "../parameters.json#/borrowernumberPathParam"
117
        }, {
118
          "name": "body",
119
          "in": "body",
120
          "description": "A JSON object containing informations about passwords",
121
          "required": true,
122
          "schema": {
123
            "type": "object",
124
            "properties": {
125
              "current_password": {
126
                "description": "Current password",
127
                "type": "string"
128
              },
129
              "new_password": {
130
                "description": "New password",
131
                "type": "string"
132
              }
133
            }
134
          }
135
        }
136
      ],
137
      "produces": [
138
          "application/json"
139
      ],
140
      "responses": {
141
        "200": {
142
          "description": "Password changed"
143
        },
144
        "400": {
145
          "description": "Bad request",
146
          "schema": {
147
            "$ref": "../definitions.json#/error"
148
          }
149
        },
150
        "403": {
151
          "description": "Access forbidden",
152
          "schema": {
153
            "$ref": "../definitions.json#/error"
154
          }
155
        },
156
        "404": {
157
          "description": "Patron not found",
158
          "schema": {
159
            "$ref": "../definitions.json#/error"
160
          }
161
        }
162
      },
163
      "x-koha-authorization": {
164
        "allow-owner": true,
165
        "permissions": {
166
          "borrowers": "1"
167
        }
168
      }
169
    }
110
  }
170
  }
111
}
171
}
(-)a/t/db_dependent/api/v1/patrons.t (-3 / +56 lines)
Lines 17-23 Link Here
17
17
18
use Modern::Perl;
18
use Modern::Perl;
19
19
20
use Test::More tests => 21;
20
use Test::More tests => 39;
21
use Test::Mojo;
21
use Test::Mojo;
22
use t::lib::TestBuilder;
22
use t::lib::TestBuilder;
23
use t::lib::Mocks;
23
use t::lib::Mocks;
Lines 25-30 use t::lib::Mocks; Link Here
25
use C4::Auth;
25
use C4::Auth;
26
use C4::Context;
26
use C4::Context;
27
27
28
use Koha::AuthUtils;
28
use Koha::Database;
29
use Koha::Database;
29
use Koha::Patron;
30
use Koha::Patron;
30
31
Lines 42-47 my $t = Test::Mojo->new('Koha::REST::V1'); Link Here
42
43
43
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
44
my $categorycode = $builder->build({ source => 'Category' })->{ categorycode };
44
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
45
my $branchcode = $builder->build({ source => 'Branch' })->{ branchcode };
46
45
my $guarantor = $builder->build({
47
my $guarantor = $builder->build({
46
    source => 'Borrower',
48
    source => 'Borrower',
47
    value => {
49
    value => {
Lines 50-55 my $guarantor = $builder->build({ Link Here
50
        flags        => 0,
52
        flags        => 0,
51
    }
53
    }
52
});
54
});
55
56
my $password = "secret";
53
my $borrower = $builder->build({
57
my $borrower = $builder->build({
54
    source => 'Borrower',
58
    source => 'Borrower',
55
    value => {
59
    value => {
Lines 86-91 $tx->req->cookies({name => 'CGISESSID', value => $session->id}); Link Here
86
$t->request_ok($tx)
90
$t->request_ok($tx)
87
  ->status_is(403);
91
  ->status_is(403);
88
92
93
89
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . ($borrower->{ borrowernumber }-1));
94
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . ($borrower->{ borrowernumber }-1));
90
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
95
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
91
$t->request_ok($tx)
96
$t->request_ok($tx)
Lines 105-116 $t->request_ok($tx) Link Here
105
  ->status_is(200)
110
  ->status_is(200)
106
  ->json_is('/guarantorid', $guarantor->{borrowernumber});
111
  ->json_is('/guarantorid', $guarantor->{borrowernumber});
107
112
113
my $password_obj = {
114
    current_password    => $password,
115
    new_password        => "new password",
116
};
117
118
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/-100/password' => json => $password_obj);
119
$t->request_ok($tx)
120
  ->status_is(401);
121
122
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$borrower->{borrowernumber}.'/password');
123
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
124
$t->request_ok($tx)
125
  ->status_is(400);
126
127
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$guarantor->{borrowernumber}.'/password' => json => $password_obj);
128
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
129
$t->request_ok($tx)
130
  ->status_is(403);
131
108
my $loggedinuser = $builder->build({
132
my $loggedinuser = $builder->build({
109
    source => 'Borrower',
133
    source => 'Borrower',
110
    value => {
134
    value => {
111
        branchcode   => $branchcode,
135
        branchcode   => $branchcode,
112
        categorycode => $categorycode,
136
        categorycode => $categorycode,
113
        flags        => 16 # borrowers flag
137
        flags        => 16, # borrowers flag
138
        password     => Koha::AuthUtils::hash_password($password),
114
    }
139
    }
115
});
140
});
116
141
Lines 135-138 $t->request_ok($tx) Link Here
135
  ->json_is('/surname' => $borrower->{ surname })
160
  ->json_is('/surname' => $borrower->{ surname })
136
  ->json_is('/lost' => Mojo::JSON->true );
161
  ->json_is('/lost' => Mojo::JSON->true );
137
162
163
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/-100/password' => json => $password_obj);
164
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
165
$t->request_ok($tx)
166
  ->status_is(404);
167
168
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
169
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
170
$t->request_ok($tx)
171
  ->status_is(200);
172
173
ok(C4::Auth::checkpw_hash($password_obj->{'new_password'}, Koha::Patrons->find($loggedinuser->{borrowernumber})->password), "New password in database.");
174
is(C4::Auth::checkpw_hash($password_obj->{'current_password'}, Koha::Patrons->find($loggedinuser->{borrowernumber})->password), "", "Old password is gone.");
175
176
$password_obj->{'current_password'} = $password_obj->{'new_password'};
177
$password_obj->{'new_password'} = "a";
178
t::lib::Mocks::mock_preference("minPasswordLength", 5);
179
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
180
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
181
$t->request_ok($tx)
182
  ->status_is(400)
183
  ->json_like('/error', qr/Password is too short/, "Password too short");
184
185
$password_obj->{'new_password'} = " abcdef ";
186
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
187
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
188
$t->request_ok($tx)
189
  ->status_is(400)
190
  ->json_is('/error', "Password cannot contain trailing whitespaces.");
191
138
$schema->storage->txn_rollback;
192
$schema->storage->txn_rollback;
139
- 

Return to bug 17006