From c52e219a8efed30a9b2e00041d02e59c67787437 Mon Sep 17 00:00:00 2001 From: David Cook Date: Mon, 19 Apr 2021 03:33:56 +0000 Subject: [PATCH] Bug 27305: Use low privilege tokens to enable authenticated iCal feed With the push of Bug 21325, it is no longer possible to authenticate via a GET request using username and password. While this is an improvement in security, it does mean that calendars like Google Calendar cannot consume private iCal feeds from Koha. This patch allows the autogeneration of unique tokens for users so that they can access their iCal feeds without their username/password. These tokens are pre-authenticated in opac-ics.pl and then passed to C4::Auth::checkauth() via get_template_and_user() for verification thanks to two very small changes in C4::Auth. Test plan: 1) Apply patch 2) Upgrade database (koha-upgrade-schema koha-dev) 3) Checkout item to patron (e.g. 39999000001310 to koha patron) 4) Log into /cgi-bin/koha/opac-user.pl 5) Right click iCal and copy link (e.g. http://localhost:8080/cgi-bin/koha/opac-ics.pl?token=facb63f231ecba45bc70aadedfdd045b) 6) Log out of OPAC 7) Go to iCal link http://localhost:8080/cgi-bin/koha/opac-ics.pl?token=facb63f231ecba45bc70aadedfdd045b 8) Observe that iCal download is performed 9) Go to OPAC home page (e.g. http://localhost:8080/cgi-bin/koha/opac-main.pl) 10) Note that you are not logged into the OPAC (FYI this is because opac-ics.pl does not send back a cookie. If it did, then you would be logged into the OPAC.) --- C4/Auth.pm | 14 ++- Koha/Auth/Shim.pm | 106 +++++++++++++++++++++ Koha/Auth/Type/Token/Ical.pm | 61 ++++++++++++ Koha/Patron.pm | 33 +++++++ .../bug27305-use-app-token-for-ical.perl | 14 +++ .../data/mysql/en/mandatory/patron_attributes.yml | 34 +++++++ .../opac-tmpl/bootstrap/en/modules/opac-user.tt | 6 +- opac/opac-ics.pl | 20 +++- opac/opac-user.pl | 5 + t/db_dependent/Auth.t | 34 ++++++- t/db_dependent/Koha/Auth/Shim.t | 52 ++++++++++ t/db_dependent/Koha/Auth/Type/Token/Ical.t | 61 ++++++++++++ t/db_dependent/Koha/Patron.t | 18 +++- 13 files changed, 453 insertions(+), 5 deletions(-) create mode 100644 Koha/Auth/Shim.pm create mode 100644 Koha/Auth/Type/Token/Ical.pm create mode 100644 installer/data/mysql/atomicupdate/bug27305-use-app-token-for-ical.perl create mode 100644 installer/data/mysql/en/mandatory/patron_attributes.yml create mode 100644 t/db_dependent/Koha/Auth/Shim.t create mode 100644 t/db_dependent/Koha/Auth/Type/Token/Ical.t diff --git a/C4/Auth.pm b/C4/Auth.pm index 1d79561533..c5fa59eb33 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -150,6 +150,7 @@ Output.pm module. sub get_template_and_user { my $in = shift; + my $extensions = shift // {}; my ( $user, $cookie, $sessionID, $flags ); # Get shibboleth login attribute @@ -168,6 +169,10 @@ sub get_template_and_user { ); if ( $in->{'template_name'} !~ m/maintenance/ ) { + my $checkauth_extensions = {}; + if ( $extensions && ref $extensions eq 'HASH' ){ + $checkauth_extensions = $extensions->{checkauth}; + } ( $user, $cookie, $sessionID, $flags ) = checkauth( $in->{'query'}, $in->{'authnotrequired'}, @@ -175,6 +180,7 @@ sub get_template_and_user { $in->{'type'}, undef, $in->{template_name}, + $checkauth_extensions, ); } @@ -817,8 +823,14 @@ sub checkauth { my $type = shift; my $emailaddress = shift; my $template_name = shift; + my $extensions = shift // {}; $type = 'opac' unless $type; + my $check_sessionID = $query->cookie("CGISESSID"); + if ($extensions && ref $extensions eq 'HASH' && $extensions->{sessionID}){ + $check_sessionID = $extensions->{sessionID}; + } + unless ( C4::Context->preference("OpacPublic") ) { my @allowed_scripts_for_private_opac = qw( opac-memberentry.tt @@ -875,7 +887,7 @@ sub checkauth { elsif ( $emailaddress) { # the Google OpenID Connect passes an email address } - elsif ( $sessionID = $query->cookie("CGISESSID") ) + elsif ( $sessionID = $check_sessionID ) { # assignment, not comparison $session = get_session($sessionID); C4::Context->_new_userenv($sessionID); diff --git a/Koha/Auth/Shim.pm b/Koha/Auth/Shim.pm new file mode 100644 index 0000000000..f49099f8c4 --- /dev/null +++ b/Koha/Auth/Shim.pm @@ -0,0 +1,106 @@ +package Koha::Auth::Shim; + +# Copyright 2021 Koha Development team +# +# 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 . + + +use Modern::Perl; + +use C4::Auth; +use C4::Context; +use Koha::Patrons; + +=head1 API + +=head2 Class Methods + +=head3 cgi_is_authenticated + + This method checks if there is a CGISESSID and if the associated session + exists and has a patron attached to it. + +=cut + +#NOTE: CGI +sub cgi_is_authenticated { + my ($class,$args) = @_; + my $is_authenticated; + my $cgi = $args->{cgi}; + if ($cgi){ + my $sessionID = $cgi->cookie('CGISESSID'); + if ($sessionID){ + my $session = C4::Auth::get_session($sessionID); + if ($session){ + my $id = $session->param('id'); + if ($id){ + my $patrons = Koha::Patrons->search({ userid => $id }); + if ($patrons->count){ + $is_authenticated = 1; + } + } + } + } + } + return $is_authenticated; +} + +=head3 create_session + + This method creates a Koha session for a patron. + +=cut + +sub create_session { + my ($class,$args) = @_; + my $complete_session; + my $patron = $args->{patron}; + if ($patron){ + my $session = C4::Auth::get_session(""); + if ($session){ + my $sessionID = $session->id; + if ($sessionID){ + C4::Context->_new_userenv($sessionID); + $session->param( 'number', $patron->borrowernumber ); + $session->param( 'id', $patron->userid ); + $session->param( 'cardnumber', $patron->cardnumber ); + $session->param( 'firstname', $patron->firstname ); + $session->param( 'surname', $patron->surname ); + $session->param( 'branch', $patron->library->branchcode ); + $session->param( 'branchname', $patron->library->branchname ); + $session->param( 'flags', $patron->flags ); + $session->param( 'emailaddress', $patron->email ); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'lasttime', time() ); + C4::Context->set_userenv( + $session->param('number'), $session->param('id'), + $session->param('cardnumber'), $session->param('firstname'), + $session->param('surname'), $session->param('branch'), + $session->param('branchname'), $session->param('flags'), + $session->param('emailaddress'), $session->param('shibboleth'), + $session->param('desk_id'), $session->param('desk_name'), + $session->param('register_id'), $session->param('register_name') + ); + #NOTE: Persist session immediately. Otherwise, scoping issues can bite you. + $session->flush(); + $complete_session = $session; + } + } + } + return $complete_session; +} + +1; diff --git a/Koha/Auth/Type/Token/Ical.pm b/Koha/Auth/Type/Token/Ical.pm new file mode 100644 index 0000000000..55d4ebe1c7 --- /dev/null +++ b/Koha/Auth/Type/Token/Ical.pm @@ -0,0 +1,61 @@ +package Koha::Auth::Type::Token::Ical; + +# Copyright 2021 Koha Development team +# +# 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 . + +use Modern::Perl; + +use Koha::Patron::Attributes; +use Koha::Patrons; +use Koha::Auth::Shim; + +=head1 API + +=head2 Class Methods + +=head3 authenticate + + This method takes a factor (ie a token) and finds the associated patron. + + On success, returns a new Koha session, which can be used to log into Koha. + +=cut + +sub authenticate { + my ($class,$args) = @_; + my $session; + my $token = $args->{token}; + if ($token){ + my $attributes = Koha::Patron::Attributes->search({ + code => 'TOKEN_ICAL', + attribute => $token, + }); + if ($attributes->count() == 1){ + my $attribute = $attributes->next; + if ($attribute){ + my $borrowernumber = $attribute->borrowernumber; + my $patron = Koha::Patrons->find( $borrowernumber ); + if ($patron){ + $session = Koha::Auth::Shim->create_session({ patron => $patron }); + } + } + } + } + return $session; +} + +1; diff --git a/Koha/Patron.pm b/Koha/Patron.pm index 8606ab0bb8..a2572c9ccd 100644 --- a/Koha/Patron.pm +++ b/Koha/Patron.pm @@ -24,6 +24,8 @@ use Carp; use List::MoreUtils qw( any uniq ); use JSON qw( to_json ); use Unicode::Normalize; +use String::Random qw( random_string ); +use Digest::MD5 qw( md5_hex ); use C4::Context; use C4::Log; @@ -1882,6 +1884,37 @@ sub queue_notice { return \%return; } +=head3 ical_auth_token + + my $ical_auth_token = $patron->ical_auth_token(); + + Fetches the patron's iCal authentication token. If token does not exist, it will be automatically generated. + +=cut + +sub ical_auth_token { + my ($self, $args) = @_; + my $ical_auth_token; + my $attribute_value = $self->get_extended_attribute( 'TOKEN_ICAL' ); + if ( $attribute_value ){ + $ical_auth_token = $attribute_value->attribute(); + } + else { + my $random_string = random_string("." x 24); + if ($random_string){ + my $digest = md5_hex($random_string); + if ($digest){ + $self->add_extended_attribute({ + code => 'TOKEN_ICAL', + attribute => $digest, + }); + $ical_auth_token = $digest; + } + } + } + return $ical_auth_token; +} + =head2 Internal methods =head3 _type diff --git a/installer/data/mysql/atomicupdate/bug27305-use-app-token-for-ical.perl b/installer/data/mysql/atomicupdate/bug27305-use-app-token-for-ical.perl new file mode 100644 index 0000000000..de3cbd16c8 --- /dev/null +++ b/installer/data/mysql/atomicupdate/bug27305-use-app-token-for-ical.perl @@ -0,0 +1,14 @@ +$DBversion = 'XXX'; # will be replaced by the RM +if( CheckVersion( $DBversion ) ) { + my $sql = q{ + INSERT INTO borrower_attribute_types (code,description,unique_id) + SELECT 'TOKEN_ICAL','Authentication token for iCal feed',1 + FROM borrower_attribute_types + WHERE NOT EXISTS ( + SELECT * FROM borrower_attribute_types WHERE code = 'TOKEN_ICAL' + ) + }; + $dbh->do($sql); + + NewVersion( $DBversion, 27305, "Add TOKEN_ICAL borrower extended attribute"); +} diff --git a/installer/data/mysql/en/mandatory/patron_attributes.yml b/installer/data/mysql/en/mandatory/patron_attributes.yml new file mode 100644 index 0000000000..bef1c2b61d --- /dev/null +++ b/installer/data/mysql/en/mandatory/patron_attributes.yml @@ -0,0 +1,34 @@ +--- +# +# Copyright 2021 Koha Development Team +# +# 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 . + +description: + - "Mandatory patron attribute types:" + - "* TOKEN_ICAL - Authentication token for iCal feed" + +tables: + - borrower_attribute_types: + translatable: [ description ] + multiline: [] + rows: + - code: "TOKEN_ICAL" + description: "Authentication token for iCal feed" + repeatable: 0 + unique_id: 1 + opac_display: 0 + staff_searchable: 0 diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt index 24b7adaf33..6a60b6b1eb 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-user.tt @@ -832,7 +832,11 @@