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

(-)a/Koha/Patron/Password/Recovery.pm (+31 lines)
Lines 23-28 use C4::Letters; Link Here
23
use Crypt::Eksblowfish::Bcrypt qw(en_base64);
23
use Crypt::Eksblowfish::Bcrypt qw(en_base64);
24
use Koha::DateUtils;
24
use Koha::DateUtils;
25
25
26
use Koha::Exceptions;
27
26
use vars qw(@ISA @EXPORT);
28
use vars qw(@ISA @EXPORT);
27
29
28
BEGIN {
30
BEGIN {
Lines 99-110 sub GetValidLinkInfo { Link Here
99
101
100
 It creates an email using the templates and sends it to the user, using the specified email
102
 It creates an email using the templates and sends it to the user, using the specified email
101
103
104
 Parameters:
105
 C<$borrower>   Koha::Patron
106
 C<$userEmail>  to_address (the one specified in the request)
107
 C<$update>     Update existing password recovery request
108
 C<$params>     Additional parameters as follows:
109
                  url:       Custom password reset link (third party integrations)
102
=cut
110
=cut
103
111
104
sub SendPasswordRecoveryEmail {
112
sub SendPasswordRecoveryEmail {
105
    my $borrower  = shift;    # Koha::Patron
113
    my $borrower  = shift;    # Koha::Patron
106
    my $userEmail = shift;    #to_address (the one specified in the request)
114
    my $userEmail = shift;    #to_address (the one specified in the request)
107
    my $update    = shift;
115
    my $update    = shift;
116
    my $params    = shift;
108
117
109
    my $schema = Koha::Database->new->schema;
118
    my $schema = Koha::Database->new->schema;
110
119
Lines 139-144 sub SendPasswordRecoveryEmail { Link Here
139
    my $uuidLink = $opacbase
148
    my $uuidLink = $opacbase
140
      . "/cgi-bin/koha/opac-password-recovery.pl?uniqueKey=$uuid_str";
149
      . "/cgi-bin/koha/opac-password-recovery.pl?uniqueKey=$uuid_str";
141
150
151
    # Custom $uuidLink. This feature is used for external integrations that handle
152
    # password resetting inside the system via e.g. Koha's REST API.
153
    if (defined $params->{url}) {
154
        # Make sure host name exists in whitelist
155
        my $url = $params->{url};
156
        my $whitelist = C4::Context->preference(
157
            'OpacResetPasswordHostWhitelist'
158
        );
159
        my @allowed_hosts = split /[,\s\|]/, $whitelist if defined $whitelist;
160
        foreach my $allowed_host (@allowed_hosts) {
161
           if ($url =~ /^https?:\/\/$allowed_host/) {
162
                $url =~ s/{uuid}/$uuid_str/gi;
163
                $uuidLink = $params->{url} = $url;
164
                last;
165
            }
166
        }
167
        Koha::Exceptions::WrongParameter->throw( error => 'System preference'
168
              . ' OpacResetPasswordHostWhitelist does'
169
              . " not contain the requested host of '$url'"
170
        ) if $uuidLink ne $params->{url};
171
    }
172
142
    # prepare the email
173
    # prepare the email
143
    my $letter = C4::Letters::GetPreparedLetter(
174
    my $letter = C4::Letters::GetPreparedLetter(
144
        module      => 'members',
175
        module      => 'members',
(-)a/Koha/REST/V1/Patrons/Password.pm (-1 / +8 lines)
Lines 79-87 sub recovery { Link Here
79
            );
79
            );
80
        }
80
        }
81
81
82
        my $link = $body->{'custom_link'};
83
82
        my $resend = ValidateBorrowernumber($patron->borrowernumber);
84
        my $resend = ValidateBorrowernumber($patron->borrowernumber);
83
85
84
        SendPasswordRecoveryEmail($patron, $patron->email, $resend);
86
        SendPasswordRecoveryEmail($patron, $patron->email, $resend, {
87
            url => $link
88
        });
85
89
86
        return $c->render(status => 201, openapi => {
90
        return $c->render(status => 201, openapi => {
87
            status => 1,
91
            status => 1,
Lines 96-101 sub recovery { Link Here
96
        if ($_->isa('Koha::Exceptions::BadParameter')) {
100
        if ($_->isa('Koha::Exceptions::BadParameter')) {
97
            return $c->render(status => 400, openapi => { error => $_->error });
101
            return $c->render(status => 400, openapi => { error => $_->error });
98
        }
102
        }
103
        elsif ($_->isa('Koha::Exceptions::WrongParameter')) {
104
            return $c->render(status => 400, openapi => { error => $_->error });
105
        }
99
        elsif ($_->isa('Koha::Exceptions::Patron::NotFound')) {
106
        elsif ($_->isa('Koha::Exceptions::Patron::NotFound')) {
100
            return $c->render(status => 404, openapi => { error => $_->error });
107
            return $c->render(status => 404, openapi => { error => $_->error });
101
        }
108
        }
(-)a/api/v1/swagger/paths/patrons.json (+4 lines)
Lines 729-734 Link Here
729
            "email": {
729
            "email": {
730
              "description": "Patron's email (required)",
730
              "description": "Patron's email (required)",
731
              "type": "string"
731
              "type": "string"
732
            },
733
            "complete_url": {
734
              "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.",
735
              "type": "string"
732
            }
736
            }
733
          },
737
          },
734
          "required": ["email"]
738
          "required": ["email"]
(-)a/installer/data/mysql/atomicupdate/Bug-19133-Password-recovery-custom-link-whitelist.perl (+11 lines)
Line 0 Link Here
1
$DBversion = 'XXX';  # will be replaced by the RM
2
if( CheckVersion( $DBversion ) ) {
3
4
    $dbh->do(
5
        "INSERT INTO systempreferences (variable, value, options, explanation, type) VALUES ('OpacResetPasswordHostWhitelist', '', '', 'Whitelist external host names for password reset in third party service. Separate list by whitespace, comma or |', 'free')"
6
    );
7
8
    # Always end with this (adjust the bug info)
9
    SetVersion( $DBversion );
10
    print "Upgrade to $DBversion done (Bug 19133 - Password recovery via REST API - whitelist custom links)\n";
11
}
(-)a/installer/data/mysql/mandatory/sysprefs.sql (+1 lines)
Lines 439-444 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
439
('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|none','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'),
439
('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|none','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'),
440
('OPACReportProblem', 0, NULL, 'Allow patrons to submit problem reports for OPAC pages to the library or Koha Administrator', 'YesNo'),
440
('OPACReportProblem', 0, NULL, 'Allow patrons to submit problem reports for OPAC pages to the library or Koha Administrator', 'YesNo'),
441
('OpacResetPassword','0','','Shows the ''Forgot your password?'' link in the OPAC','YesNo'),
441
('OpacResetPassword','0','','Shows the ''Forgot your password?'' link in the OPAC','YesNo'),
442
('OpacResetPasswordHostWhitelist','','','Whitelist external host names for password reset in third party service. Separate list by whitespace, comma or |','free'),
442
('OPACResultsLibrary', 'homebranch', 'homebranch|holdingbranch', 'Defines whether the OPAC displays the holding or home branch in search results when using XSLT', 'Choice'),
443
('OPACResultsLibrary', 'homebranch', 'homebranch|holdingbranch', 'Defines whether the OPAC displays the holding or home branch in search results when using XSLT', 'Choice'),
443
('OPACResultsSidebar','','70|10','Define HTML to be included on the search results page, underneath the facets sidebar','Textarea'),
444
('OPACResultsSidebar','','70|10','Define HTML to be included on the search results page, underneath the facets sidebar','Textarea'),
444
('OPACSearchForTitleIn','<a href=\"https://worldcat.org/search?q={TITLE}\" target=\"_blank\">Other Libraries (WorldCat)</a>\n<a href=\"https://scholar.google.com/scholar?q={TITLE}\" target=\"_blank\">Other Databases (Google Scholar)</a>\n<a href=\"https://www.bookfinder.com/search/?author={AUTHOR}&amp;title={TITLE}&amp;st=xl&amp;ac=qr\" target=\"_blank\">Online Stores (Bookfinder.com)</a>\n<a href=\"https://openlibrary.org/search?author=({AUTHOR})&title=({TITLE})\" target=\"_blank\">Open Library (openlibrary.org)</a>','70|10','Enter the HTML that will appear in the \'Search for this title in\' box on the detail page in the OPAC.  Enter {TITLE}, {AUTHOR}, or {ISBN} in place of their respective variables in the URL. Leave blank to disable \'More Searches\' menu.','Textarea'),
445
('OPACSearchForTitleIn','<a href=\"https://worldcat.org/search?q={TITLE}\" target=\"_blank\">Other Libraries (WorldCat)</a>\n<a href=\"https://scholar.google.com/scholar?q={TITLE}\" target=\"_blank\">Other Databases (Google Scholar)</a>\n<a href=\"https://www.bookfinder.com/search/?author={AUTHOR}&amp;title={TITLE}&amp;st=xl&amp;ac=qr\" target=\"_blank\">Online Stores (Bookfinder.com)</a>\n<a href=\"https://openlibrary.org/search?author=({AUTHOR})&title=({TITLE})\" target=\"_blank\">Open Library (openlibrary.org)</a>','70|10','Enter the HTML that will appear in the \'Search for this title in\' box on the detail page in the OPAC.  Enter {TITLE}, {AUTHOR}, or {ISBN} in place of their respective variables in the URL. Leave blank to disable \'More Searches\' menu.','Textarea'),
(-)a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/opac.pref (+11 lines)
Lines 388-393 OPAC: Link Here
388
                  yes: "allowed"
388
                  yes: "allowed"
389
                  no: "not allowed"
389
                  no: "not allowed"
390
            - " to recover their password via e-mail in the OPAC."
390
            - " to recover their password via e-mail in the OPAC."
391
        -
392
            - "Allow the following "
393
            - pref: OpacResetPasswordHostWhitelist
394
              type: textarea
395
            - " host names of third party services to be used as external password reset locations. Separate list elements by new line, whitespace, comma or |."
396
        -
397
            - pref: OPACPatronDetails
398
              choices:
399
                  yes: Allow
400
                  no: "Don't allow"
401
            - patrons to notify the library of changes to their contact information from the OPAC.
391
        -
402
        -
392
            - pref: OPACpatronimages
403
            - pref: OPACpatronimages
393
              choices:
404
              choices:
(-)a/t/db_dependent/api/v1/patrons_password_recovery.t (-2 / +65 lines)
Lines 43-49 my $remote_address = '127.0.0.1'; Link Here
43
my $t              = Test::Mojo->new('Koha::REST::V1');
43
my $t              = Test::Mojo->new('Koha::REST::V1');
44
44
45
subtest 'recovery() tests' => sub {
45
subtest 'recovery() tests' => sub {
46
    plan tests => 29;
46
    plan tests => 31;
47
47
48
    $schema->storage->txn_begin;
48
    $schema->storage->txn_begin;
49
49
Lines 143-148 subtest 'recovery() tests' => sub { Link Here
143
        'Password modification request found in database'
143
        'Password modification request found in database'
144
    );
144
    );
145
145
146
    my $notice_content = Koha::Notice::Messages->search({
147
        borrowernumber => $patron->borrowernumber,
148
        letter_code => 'PASSWORD_RESET',
149
        message_transport_type => 'email'
150
    }, {
151
        order_by => { '-desc' => 'message_id' }
152
    })->next->content;
153
    like($notice_content,
154
         qr/cgi-bin\/koha\/opac-password-recovery\.pl\?uniqueKey=/,
155
         'Found Koha OPAC link in email'
156
    );
157
146
    $tx = $t->ua->build_tx(POST => $url => json => {
158
    $tx = $t->ua->build_tx(POST => $url => json => {
147
        email      => $patron->email,
159
        email      => $patron->email,
148
        userid     => $patron->userid
160
        userid     => $patron->userid
Lines 181-186 subtest 'recovery() tests' => sub { Link Here
181
    })->count;
193
    })->count;
182
    is($notice, 3, 'Found password reset letters in message queue.');
194
    is($notice, 3, 'Found password reset letters in message queue.');
183
195
196
    subtest 'custom reset link' => sub {
197
        plan tests => 5;
198
199
        t::lib::Mocks::mock_preference(
200
            'OpacResetPasswordHostWhitelist', ''
201
        );
202
203
        $tx = $t->ua->build_tx(POST => $url => json => {
204
            email      => $patron->email,
205
            userid     => $patron->userid,
206
            custom_link => 'https://notallowed'
207
        });
208
        $tx->req->cookies({name => 'CGISESSID', value => $session->id});
209
        $tx->req->env({REMOTE_ADDR => '127.0.0.1'});
210
        $t->request_ok($tx)
211
          ->status_is(400);
212
213
        t::lib::Mocks::mock_preference(
214
            'OpacResetPasswordHostWhitelist', 'allowed'
215
        );
216
217
        $tx = $t->ua->build_tx(POST => $url => json => {
218
            email      => $patron->email,
219
            userid     => $patron->userid,
220
            custom_link => 'https://allowed/reset-password.pl?uniqueKey={uuid}'
221
        });
222
        $tx->req->cookies({name => 'CGISESSID', value => $session->id});
223
        $tx->req->env({REMOTE_ADDR => '127.0.0.1'});
224
        $t->request_ok($tx)
225
          ->status_is(201);
226
227
        my $rs = Koha::Database->new->schema->resultset('BorrowerPasswordRecovery');
228
        my $uuid = quotemeta $rs->search({
229
            borrowernumber => $patron->borrowernumber
230
        }, {
231
            order_by => { '-desc' => 'valid_until' }
232
        })->next->uuid;
233
        my $notice_to_external_service = Koha::Notice::Messages->search({
234
            borrowernumber => $patron->borrowernumber,
235
            letter_code => 'PASSWORD_RESET',
236
            message_transport_type => 'email'
237
        }, {
238
            order_by => { '-desc' => 'message_id' }
239
        })->next;
240
        my $content = $notice_to_external_service->content;
241
        like(
242
             $content,
243
             qr/https:\/\/allowed\/reset-password\.pl\?uniqueKey=$uuid/,
244
             'Found custom link in email'
245
        );
246
    };
247
184
    $schema->storage->txn_rollback;
248
    $schema->storage->txn_rollback;
185
};
249
};
186
250
187
- 

Return to bug 19133