Bug 31378: Add Koha::Auth::Client* modules

Signed-off-by: Lukasz Koszyk <lukasz.koszyk@kit.edu>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
Signed-off-by: Nick Clemens <nick@bywatersolutions.com>
Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Tomas Cohen Arazi <tomascohen@theke.io>
This commit is contained in:
Agustin Moyano 2022-08-18 16:29:19 -03:00 committed by Tomas Cohen Arazi
parent 5a84778724
commit 653cbeac52
Signed by: tomascohen
GPG key ID: 0A272EA1B2F3C15F
2 changed files with 316 additions and 0 deletions

198
Koha/Auth/Client.pm Normal file
View file

@ -0,0 +1,198 @@
package Koha::Auth::Client;
# Copyright Theke Solutions 2022
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use Koha::Exceptions::Auth;
use Koha::Auth::Providers;
=head1 NAME
Koha::Auth::Client - Base Koha auth client
=head1 API
=head2 Methods
=head3 new
my $auth_client = Koha::Auth::Client->new();
=cut
sub new {
my ($class) = @_;
my $self = {};
bless( $self, $class );
}
=head3 get_user
$auth_client->get_user($provider, $data)
Get user data according to provider's mapping configuration
=cut
sub get_user {
my ( $self, $params ) = @_;
my $provider_code = $params->{provider};
my $data = $params->{data};
my $interface = $params->{interface};
my $config = $params->{config};
my $provider = Koha::Auth::Providers->search({ code => $provider_code })->next;
my ( $mapped_data, $patron ) = $self->_get_data_and_patron({ provider => $provider, data => $data, config => $config });
if ($mapped_data) {
my $domain = $self->has_valid_domain_config({ provider => $provider, email => $mapped_data->{email}, interface => $interface});
$mapped_data->{categorycode} = $domain->default_category_id;
$mapped_data->{branchcode} = $domain->default_library_id;
return ( $patron, $mapped_data, $domain );
}
}
=head3 get_valid_domain_config
my $domain = Koha::Auth::Client->get_valid_domain_config(
{ provider => $provider,
email => $user_email,
interface => $interface
}
);
Gets the best suited valid domain configuration for the given provider.
=cut
sub get_valid_domain_config {
# FIXME: Should be a hashref param
my ( $self, $params ) = @_;
my $provider = $params->{provider};
my $user_email = $params->{email};
my $interface = $params->{interface};
my $domains = $provider->domains;
my $pattern = '@';
my $allow = "allow_$interface";
my @subdomain_matches;
my $default_match;
while ( my $domain = $domains->next ) {
next unless $domain->$allow;
my $domain_text = $domain->domain;
unless ( defined $domain_text && $domain_text ne '') {
$default_match = $domain;
next;
}
my ( $asterisk, $domain_name ) = ( $domain_text =~ /^(\*)?(.+)$/ );
if ( $asterisk eq '*' ) {
$pattern .= '.*';
}
$domain_name =~ s/\./\\\./g;
$pattern .= $domain_name . '$';
if ( $user_email =~ /$pattern/ ) {
if ( $asterisk eq '*' ) {
push @subdomain_matches, { domain => $domain, match_length => length $domain_name };
} else {
# Perfect match.. return this one.
return $domain;
}
}
}
if ( @subdomain_matches ) {
@subdomain_matches = sort { $b->{match_length} <=> $a->{match_length} } @subdomain_matches
unless scalar @subdomain_matches == 1;
return $subdomain_matches[0]->{domain};
}
return $default_match || 0;
}
=head3 has_valid_domain_config
my $has_valid_domain = Koha::Auth::Client->has_valid_domain_config(
{ provider => $provider,
email => $user_email,
interface => $interface
}
);
Checks if provider has a valid domain for user email. If has, returns that domain.
=cut
sub has_valid_domain_config {
# FIXME: Should be a hashref param
my ( $self, $params ) = @_;
my $domain = $self->get_valid_domain_config( $params );
Koha::Exceptions::Auth::NoValidDomain->throw( code => 401 )
unless $domain;
return $domain;
}
=head3 _get_data_and_patron
my $mapping = $auth_client->_get_data_and_patron(
{ provider => $provider,
data => $data,
config => $config
}
);
Generic method that maps raw data to patron schema, and returns a patron if it can.
Note: this only returns an empty I<hashref>. Each class should have its
own mapping returned.
=cut
sub _get_data_and_patron {
return {};
}
=head3 _tranverse_hash
my $value = $auth_client->_tranverse_hash( { base => $base_hash, keys => $key_string } );
Get deep nested value in a hash.
=cut
sub _tranverse_hash {
my ($self, $params) = @_;
my $base = $params->{base};
my $keys = $params->{keys};
my ($key, $rest) = ($keys =~ /^([^.]+)(?:\.(.*))?/);
return unless defined $key;
my $value = ref $base eq 'HASH' ? $base->{$key} : $base->[$key];
return $value unless $rest;
return $self->_tranverse_hash({ base => $value, keys => $rest });
}
1;

118
Koha/Auth/Client/OAuth.pm Normal file
View file

@ -0,0 +1,118 @@
package Koha::Auth::Client::OAuth;
# Copyright Theke Solutions 2022
#
# This file is part of Koha.
#
# Koha is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# Koha is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Koha; if not, see <http://www.gnu.org/licenses>.
use Modern::Perl;
use JSON qw( decode_json );
use MIME::Base64 qw{ decode_base64url };
use Koha::Patrons;
use Mojo::UserAgent;
use Mojo::Parameters;
use base qw( Koha::Auth::Client );
=head1 NAME
Koha::Auth::Client::OAuth - Koha OAuth Client
=head1 API
=head2 Class methods
=head3 _get_data_and_patron
my $mapping = $object->_get_data_and_patron(
{ provider => $provider,
data => $data,
config => $config
}
);
Maps OAuth raw data to a patron schema, and returns a patron if it can.
=cut
sub _get_data_and_patron {
my ( $self, $params ) = @_;
my $provider = $params->{provider};
my $data = $params->{data};
my $config = $params->{config};
my $patron;
my $mapped_data;
my $mapping = decode_json( $provider->mapping );
my $matchpoint = $provider->matchpoint;
if ( $data->{id_token} ) {
my ( $header_part, $claims_part, $footer_part ) = split( /\./, $data->{id_token} );
my $claim = decode_json( decode_base64url($claims_part) );
foreach my $key ( keys %$mapping ) {
my $pkey = $mapping->{$key};
$mapped_data->{$key} = $claim->{$pkey}
if defined $claim->{$pkey};
}
my $value = $mapped_data->{$matchpoint};
my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
if ( defined $value and $matchpoint_rs->count ) {
$patron = $matchpoint_rs->next;
}
return ( $mapped_data, $patron )
if $patron;
}
if ( defined $config->{userinfo_url} ) {
my $access_token = $data->{access_token};
my $ua = Mojo::UserAgent->new;
my $tx = $ua->get( $config->{userinfo_url} => { Authorization => "Bearer $access_token" } );
my $code = $tx->res->code || 'No response';
return if $code ne '200';
my $claim =
$tx->res->headers->content_type =~ m!^(application/json|text/javascript)(;\s*charset=\S+)?$!
? $tx->res->json
: Mojo::Parameters->new( $tx->res->body )->to_hash;
foreach my $key ( keys %$mapping ) {
my $pkey = $mapping->{$key};
my $value = $self->_tranverse_hash( { base => $claim, keys => $pkey } );
$mapped_data->{$key} = $value
if defined $value;
}
my $value = $mapped_data->{$matchpoint};
my $matchpoint_rs = Koha::Patrons->search( { $matchpoint => $value } );
if ( defined $value and $matchpoint_rs->count ) {
$patron = $matchpoint_rs->next;
}
return ( $mapped_data, $patron )
if $patron;
}
}
1;