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

(-)a/Koha/Patron/Password/Recovery.pm (+12 lines)
Lines 106-111 sub GetValidLinkInfo { Link Here
106
 C<$update>     Update existing password recovery request
106
 C<$update>     Update existing password recovery request
107
 C<$params>     Additional parameters as follows:
107
 C<$params>     Additional parameters as follows:
108
                  url:       Custom password reset link (third party integrations)
108
                  url:       Custom password reset link (third party integrations)
109
                  skip_mail: If true, will not enqueue the letter, but return it
110
                             instead. Use this flag if you want a third party
111
                             service to handle emailing instead of Koha.
109
=cut
112
=cut
110
113
111
sub SendPasswordRecoveryEmail {
114
sub SendPasswordRecoveryEmail {
Lines 179-184 sub SendPasswordRecoveryEmail { Link Here
179
          { passwordreseturl => $uuidLink, user => $borrower->userid },
182
          { passwordreseturl => $uuidLink, user => $borrower->userid },
180
    );
183
    );
181
184
185
    if ($params->{skip_mail}) {
186
        return {
187
            to_address => $userEmail,
188
            uuid       => $uuid_str,
189
            uuidLink   => $uuidLink,
190
            letter     => $letter->{content},
191
        };
192
    }
193
182
    # define to/from emails
194
    # define to/from emails
183
    my $kohaEmail = C4::Context->preference('KohaAdminEmailAddress');    # from
195
    my $kohaEmail = C4::Context->preference('KohaAdminEmailAddress');    # from
184
196
(-)a/Koha/REST/V1/Patron/Password.pm (-3 / +32 lines)
Lines 31-36 use Koha::Patron::Password::Recovery qw( Link Here
31
use Koha::Patrons;
31
use Koha::Patrons;
32
32
33
use Koha::Exceptions;
33
use Koha::Exceptions;
34
use Koha::Exceptions::Authentication;
35
use Koha::Exceptions::Authorization;
34
36
35
use Try::Tiny;
37
use Try::Tiny;
36
38
Lines 70-82 sub recovery { Link Here
70
        }
72
        }
71
73
72
        my $link = $body->{'custom_link'};
74
        my $link = $body->{'custom_link'};
73
75
        my $skip_mail =  0;
76
        if ($body->{'skip_mail'}) {
77
            Koha::Exceptions::Authentication::Required->throw(
78
                error => 'Authentication required while skip_mail parameter is on'
79
            ) unless ref($c->stash('koha.user')) eq 'Koha::Patron';
80
            if (my $user = $c->stash('koha.user')) {
81
                Koha::Exceptions::Authorization::Unauthorized->throw(
82
                    error => 'Permission get_password_reset_uuid required while '
83
                            .'skip_mail parameter is on'
84
                ) unless C4::Auth::haspermission($user->userid, {
85
                    borrowers => 'get_password_reset_uuid'
86
                });
87
                $skip_mail = 1;
88
            }
89
        }
90
        
74
        my $resend = ValidateBorrowernumber($patron->borrowernumber);
91
        my $resend = ValidateBorrowernumber($patron->borrowernumber);
75
92
76
        SendPasswordRecoveryEmail($patron, $patron->email, $resend, {
93
        my $ret = SendPasswordRecoveryEmail($patron, $patron->email, $resend, {
77
            url => $link
94
            url => $link,
95
            skip_mail => $skip_mail,
78
        });
96
        });
79
97
98
        if (ref($ret) eq 'HASH') {
99
            $ret->{status} = 2;
100
            return $c->render(status => 201, openapi => $ret);
101
        }
102
80
        return $c->render(status => 201, openapi => {
103
        return $c->render(status => 201, openapi => {
81
            status => 1,
104
            status => 1,
82
            to_address => $patron->email
105
            to_address => $patron->email
Lines 92-97 sub recovery { Link Here
92
        elsif ($_->isa('Koha::Exceptions::WrongParameter')) {
115
        elsif ($_->isa('Koha::Exceptions::WrongParameter')) {
93
            return $c->render(status => 400, openapi => { error => $_->error });
116
            return $c->render(status => 400, openapi => { error => $_->error });
94
        }
117
        }
118
        elsif ($_->isa('Koha::Exceptions::Authentication::Required')) {
119
            return $c->render(status => 401, openapi => { error => $_->error });
120
        }
121
        elsif ($_->isa('Koha::Exceptions::Authorization::Unauthorized')) {
122
            return $c->render(status => 403, openapi => { error => $_->error });
123
        }
95
        Koha::Exceptions::rethrow_exception($_);
124
        Koha::Exceptions::rethrow_exception($_);
96
    };
125
    };
97
}
126
}
(-)a/api/v1/swagger/definitions/passwordRecovery.json (-1 / +13 lines)
Lines 1-12 Link Here
1
{
1
{
2
  "type": "object",
2
  "type": "object",
3
  "properties": {
3
  "properties": {
4
    "letter": {
5
      "description": "Content of the password recovery letter (returned only if Koha skips letter enqueueing)",
6
      "type": "string"
7
    },
4
    "to_address": {
8
    "to_address": {
5
      "description": "Patron email address",
9
      "description": "Patron email address",
6
      "type": ["string", "null"]
10
      "type": ["string", "null"]
7
    },
11
    },
12
    "uuid": {
13
      "description": "Uuid of password recovery request (the token that patron can reset their password with) (returned only if Koha skips letter enqueueing)",
14
      "type": "string"
15
    },
16
    "uuidLink": {
17
      "description": "The URL that password reset can be completed at (returned only if Koha skips letter enqueueing)",
18
      "type": "string"
19
    },
8
    "status": {
20
    "status": {
9
      "description": "Status code. 1 = email has been enqueued",
21
      "description": "Status code. 1 = email has been enqueued in Koha, 2 = recovery request stored and uuid returned (but not emailed - API user should decide how to deliver the recovery request to patron)",
10
      "type": "integer"
22
      "type": "integer"
11
    }
23
    }
12
  }
24
  }
(-)a/api/v1/swagger/paths/patrons.json (+8 lines)
Lines 1177-1182 Link Here
1177
            "complete_url": {
1177
            "complete_url": {
1178
              "description": "The location where patron will complete their password recovery. This location will be added into the password recovery email. To avoid malicious use, whitelist certain hosts in OpacResetPasswordHostWhitelist system preference and forbid all other custom links. Use {uuid} for adding the password recovery uuid into your custom link. It will be replaced by the actual uuid in the email.",
1178
              "description": "The location where patron will complete their password recovery. This location will be added into the password recovery email. To avoid malicious use, whitelist certain hosts in OpacResetPasswordHostWhitelist system preference and forbid all other custom links. Use {uuid} for adding the password recovery uuid into your custom link. It will be replaced by the actual uuid in the email.",
1179
              "type": "string"
1179
              "type": "string"
1180
            },
1181
            "skip_email": {
1182
              "description": "If true, Koha will not enqueue password recovery email. Makes this endpoint return the letter, uuid and patron email. Requires permission borrowers.get_password_recovery_uuid. Useful for third party service integrations that wish to handle emailing instead of Koha.",
1183
              "type": "boolean"
1180
            }
1184
            }
1181
          },
1185
          },
1182
          "required": ["email"]
1186
          "required": ["email"]
Lines 1192-1197 Link Here
1192
          "description": "Bad parameter(s)",
1196
          "description": "Bad parameter(s)",
1193
          "schema": { "$ref": "../definitions.json#/error" }
1197
          "schema": { "$ref": "../definitions.json#/error" }
1194
        },
1198
        },
1199
        "401": {
1200
          "description": "Authentication required",
1201
          "schema": { "$ref": "../definitions.json#/error" }
1202
        },
1195
        "403": {
1203
        "403": {
1196
          "description": "Password recovery disabled, no access to this endpoint",
1204
          "description": "Password recovery disabled, no access to this endpoint",
1197
          "schema": {
1205
          "schema": {
(-)a/installer/data/mysql/atomicupdate/Bug-19133-2_Add_permission_get_password_reset_uuid.perl (+8 lines)
Line 0 Link Here
1
$DBversion = 'XXX';  # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
4
    $dbh->do( "INSERT INTO permissions (module, code, description) VALUES ( 'borrowers', 'get_password_reset_uuid', 'Allow the user to get password reset uuid when recovering passwords. Useful for third party service integrations that wish to do something with the uuid, such as handle emails themselves instead of Koha.')" );
5
6
    SetVersion( $DBversion );
7
    print "Upgrade to $DBversion done (Bug 19133 - Add permission get_password_reset_uuid.)\n";
8
}
(-)a/installer/data/mysql/userpermissions.sql (+1 lines)
Lines 11-16 INSERT INTO permissions (module, code, description) VALUES Link Here
11
   ( 'borrowers',       'view_borrowers', 'Show borrower details and search for borrowers.'),
11
   ( 'borrowers',       'view_borrowers', 'Show borrower details and search for borrowers.'),
12
   ( 'borrowers',       'manage_api_keys', "Manage Borrowers' REST API keys"),
12
   ( 'borrowers',       'manage_api_keys', "Manage Borrowers' REST API keys"),
13
   ( 'borrowers',       'get_self_service_status', 'Allow the user to get the self-service status of a borrower. Eg. can the borrower access self-service resources'),
13
   ( 'borrowers',       'get_self_service_status', 'Allow the user to get the self-service status of a borrower. Eg. can the borrower access self-service resources'),
14
   ( 'borrowers',       'get_password_reset_uuid', 'Allow the user to get password reset uuid when recovering passwords. Useful for third party service integrations that wish to do something with the uuid, such as handle emails themselves instead of Koha.'),
14
   ( 'permissions',     'set_permissions', 'Set user permissions'),
15
   ( 'permissions',     'set_permissions', 'Set user permissions'),
15
   ( 'reserveforothers','place_holds', 'Place holds for patrons'),
16
   ( 'reserveforothers','place_holds', 'Place holds for patrons'),
16
   ( 'reserveforothers','modify_holds_priority', 'Modify holds priority'),
17
   ( 'reserveforothers','modify_holds_priority', 'Modify holds priority'),
(-)a/t/db_dependent/api/v1/passwordrecovery.t (-2 / +73 lines)
Lines 44-50 my $remote_address = '127.0.0.1'; Link Here
44
my $t              = Test::Mojo->new('Koha::REST::V1');
44
my $t              = Test::Mojo->new('Koha::REST::V1');
45
45
46
subtest 'recovery() tests' => sub {
46
subtest 'recovery() tests' => sub {
47
    plan tests => 31;
47
    plan tests => 32;
48
48
49
    $schema->storage->txn_begin;
49
    $schema->storage->txn_begin;
50
50
Lines 246-251 subtest 'recovery() tests' => sub { Link Here
246
        );
246
        );
247
    };
247
    };
248
248
249
    subtest 'skip letter enqueueing' => sub {
250
        plan tests => 10;
251
252
        t::lib::Mocks::mock_preference(
253
            'OpacResetPasswordHostWhitelist', 'anotherallowed'
254
        );
255
        my ($service_borrowernumber, $service_session) = create_user_and_session({
256
            borrowers => 'get_password_reset_uuid'
257
        });
258
259
        $tx = $t->ua->build_tx(POST => $url => json => {
260
            email      => $patron->email,
261
            userid     => $patron->userid,
262
            custom_link => 'https://anotherallowed/reset-password.pl',
263
            skip_mail => Mojo::JSON->true
264
        });
265
        $tx->req->env({REMOTE_ADDR => '127.0.0.1'});
266
        $t->request_ok($tx)
267
          ->status_is(401);
268
269
        $tx = $t->ua->build_tx(POST => $url => json => {
270
            email      => $patron->email,
271
            userid     => $patron->userid,
272
            custom_link => 'https://anotherallowed/reset-password.pl',
273
            skip_mail => Mojo::JSON->true
274
        });
275
        $tx->req->cookies({name => 'CGISESSID', value => $session->id});
276
        $tx->req->env({REMOTE_ADDR => '127.0.0.1'});
277
        $t->request_ok($tx)
278
          ->status_is(403);
279
280
        Koha::Notice::Messages->search({
281
            borrowernumber => $patron->borrowernumber,
282
            letter_code => 'PASSWORD_RESET',
283
            message_transport_type => 'email'
284
        })->delete;
285
286
        $tx = $t->ua->build_tx(POST => $url => json => {
287
            email      => $patron->email,
288
            userid     => $patron->userid,
289
            custom_link => 'https://notallowed/reset-password.pl',
290
            skip_mail => Mojo::JSON->true,
291
        });
292
        $tx->req->cookies({name => 'CGISESSID', value => $service_session->id});
293
        $tx->req->env({REMOTE_ADDR => '127.0.0.1'});
294
        $t->request_ok($tx)
295
          ->status_is(400);
296
297
        $tx = $t->ua->build_tx(POST => $url => json => {
298
            email      => $patron->email,
299
            userid     => $patron->userid,
300
            custom_link => 'https://anotherallowed/reset-password.pl',
301
            skip_mail => Mojo::JSON->true,
302
        });
303
        $tx->req->cookies({name => 'CGISESSID', value => $service_session->id});
304
        $tx->req->env({REMOTE_ADDR => '127.0.0.1'});
305
        $t->request_ok($tx)
306
          ->status_is(201);
307
        my $uuid = $rs->search({
308
            borrowernumber => $patron->borrowernumber
309
        }, {
310
            order_by => { '-desc' => 'valid_until' }
311
        })->next->uuid;
312
        $t->json_is('/uuid', $uuid);
313
314
        is(Koha::Notice::Messages->search({
315
            borrowernumber => $patron->borrowernumber,
316
            letter_code => 'PASSWORD_RESET',
317
            message_transport_type => 'email'
318
        })->count, 0, 'Email not enqueued');
319
    };
320
249
    $schema->storage->txn_rollback;
321
    $schema->storage->txn_rollback;
250
};
322
};
251
323
252
- 

Return to bug 19133