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

(-)a/Koha/Auth/Client/OIDC.pm (+82 lines)
Line 0 Link Here
1
package Koha::Auth::Client::OIDC;
2
3
# This file is part of Koha.
4
#
5
# Koha is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by
7
# the Free Software Foundation; either version 3 of the License, or
8
# (at your option) any later version.
9
#
10
# Koha is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
# GNU General Public License for more details.
14
#
15
# You should have received a copy of the GNU General Public License
16
# along with Koha; if not, see <http://www.gnu.org/licenses>.
17
18
use Modern::Perl;
19
20
use JSON;
21
use MIME::Base64 qw{ decode_base64url };
22
23
24
sub ValidateIdToken {
25
    my ($json, $issuer, $clientid) = @_;
26
27
    if ( ! exists( $json->{'id_token'} ) ) {
28
        return ( undef, 'Failed to get proper credentials.' );
29
    }
30
    if ( lc( $json->{'token_type'} ) ne 'bearer' ) {
31
        return ( undef,
32
            'Authentication failed. Incorrect token type.' );
33
    }
34
    my $idtoken = $json->{'id_token'};
35
36
    # need to validate the token here
37
38
    my @segments = split( '\.', $idtoken );
39
    unless ( scalar(@segments) == 3 ) {
40
        return ( undef,
41
            'Login token broken: either too many or too few segments.' );
42
    }
43
    my ( $header, $claims, $validation ) = @segments;
44
    $claims = decode_base64url($claims);
45
    my $claims_json = decode_json($claims);
46
    if ( $issuer eq q{} ) {
47
        return ( undef, 'Unable to discover issuer.' );
48
    }
49
    if (   ( $claims_json->{'iss'} ne ( 'https://' . $issuer ) )
50
        && ( $claims_json->{'iss'} ne $issuer ) )
51
    {
52
        return ( undef,
53
            "Authentication failed. Issuer of authentication is different from what we expected."
54
        );
55
    }
56
    if ( ref( $claims_json->{'aud'} ) eq 'ARRAY' ) {
57
        warn "Audience is an array of size: "
58
            . scalar( @$claims_json->{'aud'} );
59
        if ( scalar( @$claims_json->{'aud'} ) > 1 )
60
        {    # We don't want any other audiences
61
            return ( undef,
62
                "Authentication failed. Unexpected audience provided." );
63
        }
64
    }
65
    if (   ( $claims_json->{'aud'} ne $clientid ) ) {
66
        return ( undef,
67
            "Authentication failed. Unexpected audience." );
68
    }
69
    if ( $claims_json->{'exp'} < time() ) {
70
        return ( undef, 'Sorry, your authentication has timed out.' );
71
    }
72
73
    if ( ! exists( $claims_json->{'email'} ) ) {
74
        return ( undef,
75
'Unexpectedly, no email seems to be associated with that account.'
76
        );
77
    }
78
79
    return ($claims_json, undef);
80
}
81
82
1;
(-)a/opac/svc/auth/openidconnect (-107 / +58 lines)
Lines 34-39 use C4::Auth qw{ checkauth get_session get_template_and_user }; Link Here
34
use C4::Context;
34
use C4::Context;
35
use C4::Output;
35
use C4::Output;
36
use Koha::Patrons;
36
use Koha::Patrons;
37
use Koha::Auth::Client::OIDC qw ( ValidateIdToken );
37
38
38
use LWP::UserAgent;
39
use LWP::UserAgent;
39
use HTTP::Request::Common qw{ POST };
40
use HTTP::Request::Common qw{ POST };
Lines 127-250 elsif ( defined $query->param('code') ) { Link Here
127
    );
128
    );
128
    my $response = $ua->request($request)->decoded_content;
129
    my $response = $ua->request($request)->decoded_content;
129
    my $json     = decode_json($response);
130
    my $json     = decode_json($response);
130
    if ( exists( $json->{'id_token'} ) ) {
131
    my ($claims_json, $message) = ValidateIdToken($json, $issuer, $clientid);
131
        if ( lc( $json->{'token_type'} ) ne 'bearer' ) {
132
    
132
            loginfailed( $query,
133
    if ($claims_json) {
133
                'Authentication failed. Incorrect token type.' );
134
        my $email = $claims_json->{'email'};
134
        }
135
        if (   ( $restricttodomain ne q{} )
135
        my $idtoken = $json->{'id_token'};
136
            && ( index( $email, $restricttodomain ) < 0 ) )
136
137
        # need to validate the token here
138
139
        my @segments = split( '\.', $idtoken );
140
        unless ( scalar(@segments) == 3 ) {
141
            loginfailed( $query,
142
                'Login token broken: either too many or too few segments.' );
143
        }
144
        my ( $header, $claims, $validation ) = @segments;
145
        $claims = decode_base64url($claims);
146
        my $claims_json = decode_json($claims);
147
        if ( $issuer eq q{} ) {
148
            loginfailed( $query, 'Unable to discover issuer.' );
149
        }
150
        if (   ( $claims_json->{'iss'} ne ( 'https://' . $issuer ) )
151
            && ( $claims_json->{'iss'} ne $issuer ) )
152
        {
137
        {
153
            loginfailed( $query,
138
            loginfailed( $query,
154
                "Authentication failed. Issuer of authentication is different from what we expected."
139
    'The email you have used is not valid for this library. Email addresses should conclude with '
155
            );
140
                    . $restricttodomain
156
        }
141
                    . ' .' );
157
        if ( ref( $claims_json->{'aud'} ) eq 'ARRAY' ) {
158
            warn "Audience is an array of size: "
159
              . scalar( @$claims_json->{'aud'} );
160
            if ( scalar( @$claims_json->{'aud'} ) > 1 )
161
            {    # We don't want any other audiences
162
                loginfailed( $query,
163
                    "Authentication failed. Unexpected audience provided." );
164
            }
165
        }
166
        if (   ( $claims_json->{'aud'} ne $clientid ) )
167
        {
168
            loginfailed( $query,
169
                "Authentication failed. Unexpected audience." );
170
        }
171
        if ( $claims_json->{'exp'} < time() ) {
172
            loginfailed( $query, 'Sorry, your authentication has timed out.' );
173
        }
142
        }
174
143
        else {
175
        if ( exists( $claims_json->{'email'} ) ) {
144
            my $error_feedback =
176
            my $email = $claims_json->{'email'};
145
    'The email address you are trying to use is not associated with a borrower at this library.';
177
            if (   ( $restricttodomain ne q{} )
146
            my $auto_registration = C4::Context->preference('OIDCAutoRegister') // q{0};
178
                && ( index( $email, $restricttodomain ) < 0 ) )
147
            my $borrower = Koha::Patrons->find( { email => $email } );
179
            {
148
            if (! $borrower && $auto_registration==1) {
180
                loginfailed( $query,
149
                my $firstname = $claims_json->{'given_name'} // q{};
181
'The email you have used is not valid for this library. Email addresses should conclude with '
150
                my $surname = $claims_json->{'family_name'} // q{};
182
                      . $restricttodomain
151
                if ($firstname && $surname) {
183
                      . ' .' );
152
                    my $delimiter = $firstname ? q{.} : q{};
184
            }
153
                    my $userid = $firstname . $delimiter . $surname;
185
            else {
154
                    my $categorycode = C4::Context->preference('OIDCDefaultCategory') // q{};
186
                my $error_feedback =
155
                    my $patron_category = Koha::Patron::Categories->find( $categorycode );
187
'The email address you are trying to use is not associated with a borrower at this library.';
156
                    my $branchcode = C4::Context->preference('OIDCDefaultBranch') // q{};
188
                my $auto_registration = C4::Context->preference('OIDCAutoRegister') // q{0};
157
                    my $library = Koha::Libraries->find( $branchcode );
189
                my $borrower = Koha::Patrons->find( { email => $email } );
158
                    if (defined $patron_category && defined $library) {
190
                if (! $borrower && $auto_registration==1) {
159
                        my $password = undef;
191
                    my $firstname = $claims_json->{'given_name'} // q{};
160
                        # TODO errors handling!
192
                    my $surname = $claims_json->{'family_name'} // q{};
161
                        my $borrower = Koha::Patron->new({
193
                    if ($firstname && $surname) {
162
                            firstname    => $firstname,
194
                        my $delimiter = $firstname ? q{.} : q{};
163
                            surname      => $surname,
195
                        my $userid = $firstname . $delimiter . $surname;
164
                            email        => $email,
196
                        my $categorycode = C4::Context->preference('OIDCDefaultCategory') // q{};
165
                            categorycode => $categorycode,
197
                        my $patron_category = Koha::Patron::Categories->find( $categorycode );
166
                            branchcode   => $branchcode,
198
                        my $branchcode = C4::Context->preference('OIDCDefaultBranch') // q{};
167
                            userid       => $userid,
199
                        my $library = Koha::Libraries->find( $branchcode );
168
                            password     => $password
200
                        if (defined $patron_category && defined $library) {
169
                        })->store;
201
                            my $password = undef;
202
                            # TODO errors handling!
203
                            my $borrower = Koha::Patron->new({
204
                                firstname    => $firstname,
205
                                surname      => $surname,
206
                                email        => $email,
207
                                categorycode => $categorycode,
208
                                branchcode   => $branchcode,
209
                                userid       => $userid,
210
                                password     => $password
211
                            })->store;
212
                        } else {
213
                            $error_feedback = 'The OIDCDefaultBranch or OIDCDefaultCategory system preferences are not configured properly. Please contact the library with this error message.';
214
                        }
215
                    } else {
170
                    } else {
216
                        $error_feedback = 'We could not find given name and/or family name.'
171
                        $error_feedback = 'The OIDCDefaultBranch or OIDCDefaultCategory system preferences are not configured properly. Please contact the library with this error message.';
217
                    }
172
                    }
173
                } else {
174
                    $error_feedback = 'We could not find given name and/or family name.'
218
                }
175
                }
219
                my ( $userid, $cookie, $session_id ) =
176
            }
220
                  checkauth( $query, 1, {}, 'opac', $email );
177
            my ( $userid, $cookie, $session_id ) =
221
                if ($userid) {    # A user with this email is registered in koha
178
                checkauth( $query, 1, {}, 'opac', $email );
179
            if ($userid) {    # A user with this email is registered in koha
222
180
223
                    #handle redirect to main.pl, for private opac
181
                #handle redirect to main.pl, for private opac
224
                    my $uri;
182
                my $uri;
225
                    if (C4::Context->preference('OpacPublic') ) {
183
                if (C4::Context->preference('OpacPublic') ) {
226
                        $uri    =  '/cgi-bin/koha/opac-user.pl';
184
                    $uri    =  '/cgi-bin/koha/opac-user.pl';
227
                    } else {
185
                } else {
228
                        $uri    =  '/cgi-bin/koha/opac-main.pl';
186
                    $uri    =  '/cgi-bin/koha/opac-main.pl';
229
                    }
230
                    print $query->redirect(
231
                        -uri    => $uri,
232
                        -cookie => $cookie
233
                    );
234
                }
235
                else {
236
                    loginfailed( $query, $error_feedback );
237
                }
187
                }
188
                print $query->redirect(
189
                    -uri    => $uri,
190
                    -cookie => $cookie
191
                );
192
            }
193
            else {
194
                loginfailed( $query, $error_feedback );
238
            }
195
            }
239
        }
240
        else {
241
            loginfailed( $query,
242
'Unexpectedly, no email seems to be associated with that acccount.'
243
            );
244
        }
196
        }
245
    }
197
    }
246
    else {
198
    else {
247
        loginfailed( $query, 'Failed to get proper credentials.' );
199
        loginfailed( $query, $message);
248
    }
200
    }
249
}
201
}
250
else {
202
else {
251
- 

Return to bug 30988