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

(-)a/Koha/Patron/Password/Recovery.pm (+31 lines)
Lines 22-27 use C4::Context; Link Here
22
use C4::Letters;
22
use C4::Letters;
23
use Crypt::Eksblowfish::Bcrypt qw(en_base64);
23
use Crypt::Eksblowfish::Bcrypt qw(en_base64);
24
24
25
use Koha::Exceptions;
26
25
use vars qw(@ISA @EXPORT);
27
use vars qw(@ISA @EXPORT);
26
28
27
BEGIN {
29
BEGIN {
Lines 98-109 sub GetValidLinkInfo { Link Here
98
100
99
 It creates an email using the templates and sends it to the user, using the specified email
101
 It creates an email using the templates and sends it to the user, using the specified email
100
102
103
 Parameters:
104
 C<$borrower>   Koha::Patron
105
 C<$userEmail>  to_address (the one specified in the request)
106
 C<$update>     Update existing password recovery request
107
 C<$params>     Additional parameters as follows:
108
                  url:       Custom password reset link (third party integrations)
101
=cut
109
=cut
102
110
103
sub SendPasswordRecoveryEmail {
111
sub SendPasswordRecoveryEmail {
104
    my $borrower  = shift;    # Koha::Patron
112
    my $borrower  = shift;    # Koha::Patron
105
    my $userEmail = shift;    #to_address (the one specified in the request)
113
    my $userEmail = shift;    #to_address (the one specified in the request)
106
    my $update    = shift;
114
    my $update    = shift;
115
    my $params    = shift;
107
116
108
    my $schema = Koha::Database->new->schema;
117
    my $schema = Koha::Database->new->schema;
109
118
Lines 138-143 sub SendPasswordRecoveryEmail { Link Here
138
    my $uuidLink = $opacbase
147
    my $uuidLink = $opacbase
139
      . "/cgi-bin/koha/opac-password-recovery.pl?uniqueKey=$uuid_str";
148
      . "/cgi-bin/koha/opac-password-recovery.pl?uniqueKey=$uuid_str";
140
149
150
    # Custom $uuidLink. This feature is used for external integrations that handle
151
    # password resetting inside the system via e.g. Koha's REST API.
152
    if (defined $params->{url}) {
153
        # Make sure host name exists in whitelist
154
        my $url = $params->{url};
155
        my $whitelist = C4::Context->preference(
156
            'OpacResetPasswordHostWhitelist'
157
        );
158
        my @allowed_hosts = split /[,\s\|]/, $whitelist if defined $whitelist;
159
        foreach my $allowed_host (@allowed_hosts) {
160
           if ($url =~ /^https?:\/\/$allowed_host/) {
161
                $url =~ s/{uuid}/$uuid_str/gi;
162
                $uuidLink = $params->{url} = $url;
163
                last;
164
            }
165
        }
166
        Koha::Exceptions::WrongParameter->throw( error => 'System preference'
167
              . ' OpacResetPasswordHostWhitelist does'
168
              . " not contain the requested host of '$url'"
169
        ) if $uuidLink ne $params->{url};
170
    }
171
141
    # prepare the email
172
    # prepare the email
142
    my $letter = C4::Letters::GetPreparedLetter(
173
    my $letter = C4::Letters::GetPreparedLetter(
143
        module      => 'members',
174
        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 726-731 Link Here
726
            "email": {
726
            "email": {
727
              "description": "Patron's email (required)",
727
              "description": "Patron's email (required)",
728
              "type": "string"
728
              "type": "string"
729
            },
730
            "complete_url": {
731
              "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.",
732
              "type": "string"
729
            }
733
            }
730
          },
734
          },
731
          "required": ["email"]
735
          "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/sysprefs.sql (+1 lines)
Lines 415-420 INSERT INTO systempreferences ( `variable`, `value`, `options`, `explanation`, ` Link Here
415
('OpacRenewalAllowed','0',NULL,'If ON, users can renew their issues directly from their OPAC account','YesNo'),
415
('OpacRenewalAllowed','0',NULL,'If ON, users can renew their issues directly from their OPAC account','YesNo'),
416
('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|null','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'),
416
('OpacRenewalBranch','checkoutbranch','itemhomebranch|patronhomebranch|checkoutbranch|null','Choose how the branch for an OPAC renewal is recorded in statistics','Choice'),
417
('OpacResetPassword','0','','Shows the ''Forgot your password?'' link in the OPAC','YesNo'),
417
('OpacResetPassword','0','','Shows the ''Forgot your password?'' link in the OPAC','YesNo'),
418
('OpacResetPasswordHostWhitelist','','','Whitelist external host names for password reset in third party service. Separate list by whitespace, comma or |','free'),
418
('OPACResultsLibrary', 'homebranch', 'homebranch|holdingbranch', 'Defines whether the OPAC displays the holding or home branch in search results when using XSLT', 'Choice'),
419
('OPACResultsLibrary', 'homebranch', 'homebranch|holdingbranch', 'Defines whether the OPAC displays the holding or home branch in search results when using XSLT', 'Choice'),
419
('OPACResultsSidebar','','70|10','Define HTML to be included on the search results page, underneath the facets sidebar','Textarea'),
420
('OPACResultsSidebar','','70|10','Define HTML to be included on the search results page, underneath the facets sidebar','Textarea'),
420
('OPACSearchForTitleIn','<li><a  href=\"http://worldcat.org/search?q={TITLE}\" target=\"_blank\">Other Libraries (WorldCat)</a></li>\n<li><a href=\"https://scholar.google.com/scholar?q={TITLE}\" target=\"_blank\">Other Databases (Google Scholar)</a></li>\n<li><a href=\"http://www.bookfinder.com/search/?author={AUTHOR}&amp;title={TITLE}&amp;st=xl&amp;ac=qr\" target=\"_blank\">Online Stores (Bookfinder.com)</a></li>\n<li><a href=\"https://openlibrary.org/search/?author=({AUTHOR})&title=({TITLE})\" target=\"_blank\">Open Library (openlibrary.org)</a></li>','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'),
421
('OPACSearchForTitleIn','<li><a  href=\"http://worldcat.org/search?q={TITLE}\" target=\"_blank\">Other Libraries (WorldCat)</a></li>\n<li><a href=\"https://scholar.google.com/scholar?q={TITLE}\" target=\"_blank\">Other Databases (Google Scholar)</a></li>\n<li><a href=\"http://www.bookfinder.com/search/?author={AUTHOR}&amp;title={TITLE}&amp;st=xl&amp;ac=qr\" target=\"_blank\">Online Stores (Bookfinder.com)</a></li>\n<li><a href=\"https://openlibrary.org/search/?author=({AUTHOR})&title=({TITLE})\" target=\"_blank\">Open Library (openlibrary.org)</a></li>','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 (+5 lines)
Lines 390-395 OPAC: Link Here
390
                  yes: "allowed"
390
                  yes: "allowed"
391
                  no: "not allowed"
391
                  no: "not allowed"
392
            - " to recover their password via e-mail in the OPAC"
392
            - " to recover their password via e-mail in the OPAC"
393
        -
394
            - "Allow the following "
395
            - pref: OpacResetPasswordHostWhitelist
396
              type: textarea
397
            - " host names of third party services to be used as external password reset locations. Separate list elements by new line, whitespace, comma or |."
393
        -
398
        -
394
            - pref: OPACPatronDetails
399
            - pref: OPACPatronDetails
395
              choices:
400
              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