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 630-635 sub get_enrollable_clubs { Link Here
630
    return wantarray ? $e->as_list : $e;
631
    return wantarray ? $e->as_list : $e;
631
}
632
}
632
633
634
=head2 change_password_to
635
636
my $changed = $patron->change_password_to($cleartext_password);
637
638
Changes patron's password to C<$cleartext_password>. This subroutine
639
also makes validations for new password, but does not check the old
640
one.
641
642
=cut
643
644
sub change_password_to {
645
    my ($self, $cleartext_password) = @_;
646
647
    my $min_length = C4::Context->preference("minPasswordLength");
648
    if ($min_length > length($cleartext_password)) {
649
        return (undef, "Password is too short. Minimum length: $min_length.");
650
    }
651
    if ($cleartext_password =~ m|^\s+| or $cleartext_password =~ m|\s+$|) {
652
        return (undef, "Password cannot contain trailing whitespaces.");
653
    }
654
    my $hashed_password = Koha::AuthUtils::hash_password($cleartext_password);
655
    $self->set({ password => $hashed_password })->store;
656
    logaction( "MEMBERS", "CHANGE PASS", $self->borrowernumber, "" ) if C4::Context->preference("BorrowersLog");
657
    return 1;
658
}
659
633
=head3 type
660
=head3 type
634
661
635
=cut
662
=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, 200);
46
    return $c->$cb($patron, 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 16-20 Link Here
16
  },
16
  },
17
  "/patrons/{borrowernumber}": {
17
  "/patrons/{borrowernumber}": {
18
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
18
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}"
19
  },
20
  "/patrons/{borrowernumber}/password": {
21
    "$ref": "paths/patrons.json#/~1patrons~1{borrowernumber}~1password"
19
  }
22
  }
20
}
23
}
(-)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 => 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
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 82-87 $tx->req->cookies({name => 'CGISESSID', value => $session->id}); Link Here
82
$t->request_ok($tx)
86
$t->request_ok($tx)
83
  ->status_is(403);
87
  ->status_is(403);
84
88
89
85
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . ($borrower->{ borrowernumber }-1));
90
$tx = $t->ua->build_tx(GET => "/api/v1/patrons/" . ($borrower->{ borrowernumber }-1));
86
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
91
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
87
$t->request_ok($tx)
92
$t->request_ok($tx)
Lines 101-112 $t->request_ok($tx) Link Here
101
  ->status_is(200)
106
  ->status_is(200)
102
  ->json_is('/guarantorid', $guarantor->{borrowernumber});
107
  ->json_is('/guarantorid', $guarantor->{borrowernumber});
103
108
109
my $password_obj = {
110
    current_password    => $password,
111
    new_password        => "new password",
112
};
113
114
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/-100/password' => json => $password_obj);
115
$t->request_ok($tx)
116
  ->status_is(401);
117
118
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$borrower->{borrowernumber}.'/password');
119
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
120
$t->request_ok($tx)
121
  ->status_is(400);
122
123
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$guarantor->{borrowernumber}.'/password' => json => $password_obj);
124
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
125
$t->request_ok($tx)
126
  ->status_is(403);
127
104
my $loggedinuser = $builder->build({
128
my $loggedinuser = $builder->build({
105
    source => 'Borrower',
129
    source => 'Borrower',
106
    value => {
130
    value => {
107
        branchcode   => $branchcode,
131
        branchcode   => $branchcode,
108
        categorycode => $categorycode,
132
        categorycode => $categorycode,
109
        flags        => 16 # borrowers flag
133
        flags        => 16, # borrowers flag
134
        password     => Koha::AuthUtils::hash_password($password),
110
    }
135
    }
111
});
136
});
112
137
Lines 131-134 $t->request_ok($tx) Link Here
131
  ->json_is('/surname' => $borrower->{ surname })
156
  ->json_is('/surname' => $borrower->{ surname })
132
  ->json_is('/lost' => 1 );
157
  ->json_is('/lost' => 1 );
133
158
134
$dbh->rollback;
159
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/-100/password' => json => $password_obj);
160
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
161
$t->request_ok($tx)
162
  ->status_is(404);
163
164
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
165
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
166
$t->request_ok($tx)
167
  ->status_is(200);
168
169
ok(C4::Auth::checkpw_hash($password_obj->{'new_password'}, Koha::Patrons->find($loggedinuser->{borrowernumber})->password), "New password in database.");
170
is(C4::Auth::checkpw_hash($password_obj->{'current_password'}, Koha::Patrons->find($loggedinuser->{borrowernumber})->password), "", "Old password is gone.");
171
172
$password_obj->{'current_password'} = $password_obj->{'new_password'};
173
$password_obj->{'new_password'} = "a";
174
t::lib::Mocks::mock_preference("minPasswordLength", 5);
175
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
176
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
177
$t->request_ok($tx)
178
  ->status_is(400)
179
  ->json_like('/error', qr/Password is too short/, "Password too short");
180
181
$password_obj->{'new_password'} = " abcdef ";
182
$tx = $t->ua->build_tx(PATCH => '/api/v1/patrons/'.$loggedinuser->{borrowernumber}.'/password' => json => $password_obj);
183
$tx->req->cookies({name => 'CGISESSID', value => $session->id});
184
$t->request_ok($tx)
185
  ->status_is(400)
186
  ->json_is('/error', "Password cannot contain trailing whitespaces.");
187
188
$schema->storage->txn_rollback;
135
- 

Return to bug 17006