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

(-)a/Koha/Auth/Client.pm (+198 lines)
Line 0 Link Here
1
package Koha::Auth::Client;
2
3
# Copyright Theke Solutions 2022
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use Koha::Exceptions::Auth;
23
use Koha::Auth::Providers;
24
25
=head1 NAME
26
27
Koha::Auth::Client - Base Koha auth client
28
29
=head1 API
30
31
=head2 Methods
32
33
=head3 new
34
35
    my $auth_client = Koha::Auth::Client->new();
36
37
=cut
38
39
sub new {
40
    my ($class) = @_;
41
    my $self = {};
42
43
    bless( $self, $class );
44
}
45
46
=head3 get_user
47
48
    $auth_client->get_user($provider, $data)
49
50
Get user data according to provider's mapping configuration
51
52
=cut
53
54
sub get_user {
55
    my ( $self, $params ) = @_;
56
    my $provider_code = $params->{provider};
57
    my $data          = $params->{data};
58
    my $interface     = $params->{interface};
59
    my $config        = $params->{config};
60
61
    my $provider = Koha::Auth::Providers->search({ code => $provider_code })->next;
62
63
    my ( $mapped_data, $patron ) = $self->_get_data_and_patron({ provider => $provider, data => $data, config => $config });
64
65
    if ($mapped_data) {
66
        my $domain = $self->has_valid_domain_config({ provider => $provider, email => $mapped_data->{email}, interface => $interface});
67
68
        $mapped_data->{categorycode} = $domain->default_category_id;
69
        $mapped_data->{branchcode}   = $domain->default_library_id;
70
71
        return ( $patron, $mapped_data, $domain );
72
    }
73
}
74
75
=head3 get_valid_domain_config
76
77
    my $domain = Koha::Auth::Client->get_valid_domain_config(
78
        {   provider  => $provider,
79
            email     => $user_email,
80
            interface => $interface
81
        }
82
    );
83
84
Gets the best suited valid domain configuration for the given provider.
85
86
=cut
87
88
sub get_valid_domain_config {
89
    # FIXME: Should be a hashref param
90
    my ( $self, $params ) = @_;
91
    my $provider   = $params->{provider};
92
    my $user_email = $params->{email};
93
    my $interface  = $params->{interface};
94
95
    my $domains = $provider->domains;
96
    my $pattern = '@';
97
    my $allow   = "allow_$interface";
98
    my @subdomain_matches;
99
    my $default_match;
100
101
    while ( my $domain = $domains->next ) {
102
        next unless $domain->$allow;
103
104
        my $domain_text = $domain->domain;
105
        unless ( defined $domain_text && $domain_text ne '') {
106
            $default_match = $domain;
107
            next;
108
        }
109
        my ( $asterisk, $domain_name ) = ( $domain_text =~ /^(\*)?(.+)$/ );
110
        if ( $asterisk eq '*' ) {
111
            $pattern .= '.*';
112
        }
113
        $domain_name =~ s/\./\\\./g;
114
        $pattern .= $domain_name . '$';
115
        if ( $user_email =~ /$pattern/ ) {
116
            if ( $asterisk eq '*' ) {
117
                push @subdomain_matches, { domain => $domain, match_length => length $domain_name };
118
            } else {
119
120
                # Perfect match.. return this one.
121
                return $domain;
122
            }
123
        }
124
    }
125
126
    if ( @subdomain_matches ) {
127
        @subdomain_matches = sort { $b->{match_length} <=> $a->{match_length} } @subdomain_matches
128
          unless scalar @subdomain_matches == 1;
129
        return $subdomain_matches[0]->{domain};
130
    }
131
132
    return $default_match || 0;
133
}
134
135
=head3 has_valid_domain_config
136
137
    my $has_valid_domain = Koha::Auth::Client->has_valid_domain_config(
138
        {   provider  => $provider,
139
            email     => $user_email,
140
            interface => $interface
141
        }
142
    );
143
144
Checks if provider has a valid domain for user email. If has, returns that domain.
145
146
=cut
147
148
sub has_valid_domain_config {
149
    # FIXME: Should be a hashref param
150
    my ( $self, $params ) = @_;
151
    my $domain = $self->get_valid_domain_config( $params );
152
153
    Koha::Exceptions::Auth::NoValidDomain->throw( code => 401 )
154
      unless $domain;
155
156
    return $domain;
157
}
158
159
=head3 _get_data_and_patron
160
161
    my $mapping = $auth_client->_get_data_and_patron(
162
        {   provider => $provider,
163
            data     => $data,
164
            config   => $config
165
        }
166
    );
167
168
Generic method that maps raw data to patron schema, and returns a patron if it can.
169
170
Note: this only returns an empty I<hashref>. Each class should have its
171
own mapping returned.
172
173
=cut
174
175
sub _get_data_and_patron {
176
    return {};
177
}
178
179
=head3 _tranverse_hash
180
181
    my $value = $auth_client->_tranverse_hash( { base => $base_hash, keys => $key_string } );
182
183
Get deep nested value in a hash.
184
185
=cut
186
187
sub _tranverse_hash {
188
    my ($self, $params) = @_;
189
    my $base = $params->{base};
190
    my $keys = $params->{keys};
191
    my ($key, $rest) = ($keys =~ /^([^.]+)(?:\.(.*))?/);
192
    return unless defined $key;
193
    my $value = ref $base eq 'HASH' ? $base->{$key} : $base->[$key];
194
    return $value unless $rest;
195
    return $self->_tranverse_hash({ base => $value, keys => $rest });
196
}
197
198
1;
(-)a/Koha/Auth/Client/OAuth.pm (-1 / +118 lines)
Line 0 Link Here
0
- 
1
package Koha::Auth::Client::OAuth;
2
3
# Copyright Theke Solutions 2022
4
#
5
# This file is part of Koha.
6
#
7
# Koha is free software; you can redistribute it and/or modify it
8
# under the terms of the GNU General Public License as published by
9
# the Free Software Foundation; either version 3 of the License, or
10
# (at your option) any later version.
11
#
12
# Koha is distributed in the hope that it will be useful, but
13
# WITHOUT ANY WARRANTY; without even the implied warranty of
14
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
# GNU General Public License for more details.
16
#
17
# You should have received a copy of the GNU General Public License
18
# along with Koha; if not, see <http://www.gnu.org/licenses>.
19
20
use Modern::Perl;
21
22
use JSON qw( decode_json );
23
use MIME::Base64 qw{ decode_base64url };
24
use Koha::Patrons;
25
use Mojo::UserAgent;
26
use Mojo::Parameters;
27
28
use base qw( Koha::Auth::Client );
29
30
=head1 NAME
31
32
Koha::Auth::Client::OAuth - Koha OAuth Client
33
34
=head1 API
35
36
=head2 Class methods
37
38
=head3 _get_data_and_patron
39
40
    my $mapping = $object->_get_data_and_patron(
41
        {   provider => $provider,
42
            data     => $data,
43
            config   => $config
44
        }
45
    );
46
47
Maps OAuth raw data to a patron schema, and returns a patron if it can.
48
49
=cut
50
51
sub _get_data_and_patron {
52
    my ( $self, $params ) = @_;
53
54
    my $provider = $params->{provider};
55
    my $data     = $params->{data};
56
    my $config   = $params->{config};
57
58
    my $patron;
59
    my $mapped_data;
60
61
    my $mapping = decode_json( $provider->mapping );
62
    my $matchpoint = $provider->matchpoint;
63
64
    if ( $data->{id_token} ) {
65
        my ( $header_part, $claims_part, $footer_part ) = split( /\./, $data->{id_token} );
66
67
        my $claim = decode_json( decode_base64url($claims_part) );
68
        foreach my $key ( keys %$mapping ) {
69
            my $pkey = $mapping->{$key};
70
            $mapped_data->{$key} = $claim->{$pkey}
71
              if defined $claim->{$pkey};
72
        }
73
74
        my $value = $mapped_data->{$matchpoint};
75
76
        my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
77
78
        if ( defined $value and $matchpoint_rs->count ) {
79
            $patron = $matchpoint_rs->next;
80
        }
81
82
        return ( $mapped_data, $patron )
83
          if $patron;
84
    }
85
86
    if ( defined $config->{userinfo_url} ) {
87
        my $access_token = $data->{access_token};
88
        my $ua           = Mojo::UserAgent->new;
89
        my $tx           = $ua->get( $config->{userinfo_url} => { Authorization => "Bearer $access_token" } );
90
        my $code         = $tx->res->code || 'No response';
91
92
        return if $code ne '200';
93
        my $claim =
94
            $tx->res->headers->content_type =~ m!^(application/json|text/javascript)(;\s*charset=\S+)?$!
95
          ? $tx->res->json
96
          : Mojo::Parameters->new( $tx->res->body )->to_hash;
97
98
        foreach my $key ( keys %$mapping ) {
99
            my $pkey  = $mapping->{$key};
100
            my $value = $self->_tranverse_hash( { base => $claim, keys => $pkey } );
101
            $mapped_data->{$key} = $value
102
              if defined $value;
103
        }
104
105
        my $value = $mapped_data->{$matchpoint};
106
107
        my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
108
109
        if ( defined $value and $matchpoint_rs->count ) {
110
            $patron = $matchpoint_rs->next;
111
        }
112
113
        return ( $mapped_data, $patron )
114
          if $patron;
115
    }
116
}
117
118
1;

Return to bug 31378