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

(-)a/Koha/Auth/Client/OIDC.pm (+88 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
use vars qw(@ISA @EXPORT);
24
BEGIN {
25
    require Exporter;
26
    @ISA = qw( Exporter );
27
    push @EXPORT, qw( ValidateIdToken );
28
};
29
30
sub ValidateIdToken {
31
    my ($json, $issuer, $clientid) = @_;
32
33
    if ( ! exists( $json->{'id_token'} ) ) {
34
        return ( undef, 'Failed to get proper credentials.' );
35
    }
36
    if ( lc( $json->{'token_type'} ) ne 'bearer' ) {
37
        return ( undef,
38
            'Authentication failed. Incorrect token type.' );
39
    }
40
    my $idtoken = $json->{'id_token'};
41
42
    # need to validate the token here
43
44
    my @segments = split( '\.', $idtoken );
45
    unless ( scalar(@segments) == 3 ) {
46
        return ( undef,
47
            'Login token broken: either too many or too few segments.' );
48
    }
49
    my ( $header, $claims, $validation ) = @segments;
50
    $claims = decode_base64url($claims);
51
    my $claims_json = decode_json($claims);
52
    if ( $issuer eq q{} ) {
53
        return ( undef, 'Unable to discover issuer.' );
54
    }
55
    if (   ( $claims_json->{'iss'} ne ( 'https://' . $issuer ) )
56
        && ( $claims_json->{'iss'} ne $issuer ) )
57
    {
58
        return ( undef,
59
            "Authentication failed. Issuer of authentication is different from what we expected."
60
        );
61
    }
62
    if ( ref( $claims_json->{'aud'} ) eq 'ARRAY' ) {
63
        warn "Audience is an array of size: "
64
            . scalar( @$claims_json->{'aud'} );
65
        if ( scalar( @$claims_json->{'aud'} ) > 1 )
66
        {    # We don't want any other audiences
67
            return ( undef,
68
                "Authentication failed. Unexpected audience provided." );
69
        }
70
    }
71
    if (   ( $claims_json->{'aud'} ne $clientid ) ) {
72
        return ( undef,
73
            "Authentication failed. Unexpected audience." );
74
    }
75
    if ( $claims_json->{'exp'} < time() ) {
76
        return ( undef, 'Sorry, your authentication has timed out.' );
77
    }
78
79
    if ( ! exists( $claims_json->{'email'} ) ) {
80
        return ( undef,
81
'Unexpectedly, no email seems to be associated with that account.'
82
        );
83
    }
84
85
    return ($claims_json, undef);
86
}
87
88
1;
(-)a/opac/svc/auth/openidconnect (-106 / +57 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
            loginfailed( $query,
133
                'Authentication failed. Incorrect token type.' );
134
        }
135
        my $idtoken = $json->{'id_token'};
136
132
137
        # need to validate the token here
133
    if ($claims_json) {
138
134
        my $email = $claims_json->{'email'};
139
        my @segments = split( '\.', $idtoken );
135
        if (   ( $restricttodomain ne q{} )
140
        unless ( scalar(@segments) == 3 ) {
136
            && ( index( $email, $restricttodomain ) < 0 ) )
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
141
                    . ' .' );
156
        }
142
        }
157
        if ( ref( $claims_json->{'aud'} ) eq 'ARRAY' ) {
143
        else {
158
            warn "Audience is an array of size: "
144
            my $error_feedback =
159
              . scalar( @$claims_json->{'aud'} );
145
    'The email address you are trying to use is not associated with a borrower at this library.';
160
            if ( scalar( @$claims_json->{'aud'} ) > 1 )
146
            my $auto_registration = C4::Context->preference('OIDCAutoRegister') // q{0};
161
            {    # We don't want any other audiences
147
            my $borrower = Koha::Patrons->find( { email => $email } );
162
                loginfailed( $query,
148
            if (! $borrower && $auto_registration==1) {
163
                    "Authentication failed. Unexpected audience provided." );
149
                my $firstname = $claims_json->{'given_name'} // q{};
164
            }
150
                my $surname = $claims_json->{'family_name'} // q{};
165
        }
151
                if ($firstname && $surname) {
166
        if (   ( $claims_json->{'aud'} ne $clientid ) )
152
                    my $delimiter = $firstname ? q{.} : q{};
167
        {
153
                    my $userid = $firstname . $delimiter . $surname;
168
            loginfailed( $query,
154
                    my $categorycode = C4::Context->preference('OIDCDefaultCategory') // q{};
169
                "Authentication failed. Unexpected audience." );
155
                    my $patron_category = Koha::Patron::Categories->find( $categorycode );
170
        }
156
                    my $branchcode = C4::Context->preference('OIDCDefaultBranch') // q{};
171
        if ( $claims_json->{'exp'} < time() ) {
157
                    my $library = Koha::Libraries->find( $branchcode );
172
            loginfailed( $query, 'Sorry, your authentication has timed out.' );
158
                    if (defined $patron_category && defined $library) {
173
        }
159
                        my $password = undef;
174
160
                        # TODO errors handling!
175
        if ( exists( $claims_json->{'email'} ) ) {
161
                        my $borrower = Koha::Patron->new({
176
            my $email = $claims_json->{'email'};
162
                            firstname    => $firstname,
177
            if (   ( $restricttodomain ne q{} )
163
                            surname      => $surname,
178
                && ( index( $email, $restricttodomain ) < 0 ) )
164
                            email        => $email,
179
            {
165
                            categorycode => $categorycode,
180
                loginfailed( $query,
166
                            branchcode   => $branchcode,
181
'The email you have used is not valid for this library. Email addresses should conclude with '
167
                            userid       => $userid,
182
                      . $restricttodomain
168
                            password     => $password
183
                      . ' .' );
169
                        })->store;
184
            }
185
            else {
186
                my $error_feedback =
187
'The email address you are trying to use is not associated with a borrower at this library.';
188
                my $auto_registration = C4::Context->preference('OIDCAutoRegister') // q{0};
189
                my $borrower = Koha::Patrons->find( { email => $email } );
190
                if (! $borrower && $auto_registration==1) {
191
                    my $firstname = $claims_json->{'given_name'} // q{};
192
                    my $surname = $claims_json->{'family_name'} // q{};
193
                    if ($firstname && $surname) {
194
                        my $delimiter = $firstname ? q{.} : q{};
195
                        my $userid = $firstname . $delimiter . $surname;
196
                        my $categorycode = C4::Context->preference('OIDCDefaultCategory') // q{};
197
                        my $patron_category = Koha::Patron::Categories->find( $categorycode );
198
                        my $branchcode = C4::Context->preference('OIDCDefaultBranch') // q{};
199
                        my $library = Koha::Libraries->find( $branchcode );
200
                        if (defined $patron_category && defined $library) {
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