From 34f9335991cec4ad5b3484dff976d61a5da73eb0 Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Mon, 3 Aug 2015 15:10:20 +0300 Subject: [PATCH] Bug 7174 - Authentication Rewrite - Core Depends heavily on Buugg 14540, which introduces many tests to spot regression caused by this feature. Introduces a new modular authentication system based on Exception signaling and reusable authentication components/challenges. This system is cross-framework -compatible, currently supporting Mojolicious and CGI, but adding support for any other framework is straightforward. Central idea is to provide a single authentication function to deal with all types of authentication scenarios, REST API, Cookie, Password, LDAP... Also it is important to make a system that is easy to extend and can deal with many future authentication scenarios. Currently only CGI password, LDAP and cookie login is tested. Legacy behaviour is used to deal with other types of authentication. See the attached schematic in Bugzilla for a more architectural overview. Also fixes few bugs related to anonymous_search_history The basic principle is: 1. We get an authentication request from any Web/GUI-framework, for ex CGI. 2. Request is normalized by extracting all necessary authentication data elements to a separate data structure. 3. Based on the found authentication data elements, system decides which authentication route to take. 4. Route implements all challenges needed to authenticate the request. Route returns the authenticated Koha::Borrower or an Exception if login failed. 5. The user environment/session is set/deleted based on the Route result. 6. a Koha::Borrower and the CGISESSID-cookie is returned to the calling framework in the format the framework needs. 7. Framework needs to catch possible exceptions and deal with them. Eg. login failed, no permission, under maintenance. 8. Authentication succeeds and session is set, or failure is reported to user. --- Koha/Auth.pm | 258 +++++++++++++++++++++ Koha/Auth/Challenge.pm | 74 ++++++ Koha/Auth/Challenge/Cookie.pm | 88 +++++++ .../Challenge/IndependentBranchesAutolocation.pm | 53 +++++ Koha/Auth/Challenge/OPACMaintenance.pm | 44 ++++ Koha/Auth/Challenge/Password.pm | 127 ++++++++++ Koha/Auth/Challenge/Permission.pm | 42 ++++ Koha/Auth/Challenge/RESTV1.pm | 179 ++++++++++++++ Koha/Auth/Challenge/Version.pm | 56 +++++ Koha/Auth/RequestNormalizer.pm | 178 ++++++++++++++ Koha/Auth/Route.pm | 75 ++++++ Koha/Auth/Route/Cookie.pm | 44 ++++ Koha/Auth/Route/Password.pm | 46 ++++ Koha/Auth/Route/RESTV1.pm | 43 ++++ koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt | 4 +- .../opac-tmpl/bootstrap/en/modules/opac-auth.tt | 4 +- opac/opac-search-history.pl | 1 - opac/opac-user.pl | 2 +- t/db_dependent/Koha/Borrower.t | 55 +++++ 19 files changed, 1369 insertions(+), 4 deletions(-) create mode 100644 Koha/Auth.pm create mode 100644 Koha/Auth/Challenge.pm create mode 100644 Koha/Auth/Challenge/Cookie.pm create mode 100644 Koha/Auth/Challenge/IndependentBranchesAutolocation.pm create mode 100644 Koha/Auth/Challenge/OPACMaintenance.pm create mode 100644 Koha/Auth/Challenge/Password.pm create mode 100644 Koha/Auth/Challenge/Permission.pm create mode 100644 Koha/Auth/Challenge/RESTV1.pm create mode 100644 Koha/Auth/Challenge/Version.pm create mode 100644 Koha/Auth/RequestNormalizer.pm create mode 100644 Koha/Auth/Route.pm create mode 100644 Koha/Auth/Route/Cookie.pm create mode 100644 Koha/Auth/Route/Password.pm create mode 100644 Koha/Auth/Route/RESTV1.pm create mode 100644 t/db_dependent/Koha/Borrower.t diff --git a/Koha/Auth.pm b/Koha/Auth.pm new file mode 100644 index 0000000..17d7ae3 --- /dev/null +++ b/Koha/Auth.pm @@ -0,0 +1,258 @@ +package Koha::Auth; + +# Copyright 2015 Vaara-kirjastot +# +# 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 . + +#Define common packages +use Modern::Perl; +use Scalar::Util qw(blessed); +use Try::Tiny; + +#Define Koha packages +use Koha::Auth::RequestNormalizer; +use Koha::Auth::Route::Password; +use Koha::Auth::Route::Cookie; +use Koha::Auth::Route::RESTV1; + +#Define Exceptions +use Koha::Exception::BadParameter; +use Koha::Exception::Logout; +use Koha::Exception::UnknownProgramState; + +use Koha::Libraries; + +#Define the headers, POST-parameters and cookies extracted from the various web-frameworks' +# request-objects and passed to the authentication system as normalized values. +our @authenticationHeaders = ('X-Koha-Date', 'Authorization'); +our @authenticationPOSTparams = ('password', 'userid', 'cardnumber', 'PT', 'branch', 'logout.x', 'koha_login_context'); +our @authenticationCookies = ('CGISESSID'); #Really we should have only one of these. + +=head authenticate + +@PARAM3 HASHRef of authentication directives. Supported values: + 'inOPAC' => 1, #Authentication context is in OPAC + 'inREST' => 'v1', #Authentication context is in REST API V1 + 'inSC' => 1, #Authentication context is in the staff client + 'authnotrequired' => 1, #Disregard all Koha::Exception::LoginFailed||NoPermission-exceptions, + #and authenticate as an anonymous user if normal authentication + #fails. +@THROWS Koha::Exception::VersionMismatch + Koha::Exception::BadSystemPreference + Koha::Exception::BadParameter + Koha::Exception::ServiceTemporarilyUnavailable + Koha::Exception::LoginFailed + Koha::Exception::NoPermission + Koha::Exception::Logout, catch this and redirect the request to the logout page. +=cut + +sub authenticate { + my ($controller, $permissions, $authParams) = @_; + my $rae = _authenticate_validateAndNormalizeParameters(@_); #Get the normalized request authentication elements + + my $borrower; #Each authentication route returns a Koha::Patron-object on success. We use this to generate the Context() + + ##Select the Authentication route. + ##Routes are introduced in priority order, and if one matches, the other routes are ignored. + try { + #0. Logout + if ($rae->{postParams}->{'logout.x'}) { + clearUserEnvironment($rae->{cookies}->{CGISESSID}, $authParams); + Koha::Exception::Logout->throw(error => "User logged out. Please redirect me!"); + } + #1. Check for password authentication, including LDAP. + if (not($borrower) && $rae->{postParams}->{koha_login_context} && ($rae->{postParams}->{userid} || $rae->{postParams}->{cardnumber}) && $rae->{postParams}->{password}) { + $borrower = Koha::Auth::Route::Password::challenge($rae, $permissions, $authParams); + } + #2. Check for REST's signature-based authentication. + #elsif ($rae->{headers}->{'Authorization'} && $rae->{headers}->{'Authorization'} =~ /Koha/) { + if (not($borrower) && $rae->{headers}->{'Authorization'}) { + $borrower = Koha::Auth::Route::RESTV1::challenge($rae, $permissions, $authParams); + } + #3. Check for the cookie. If cookies go stale, they block all subsequent authentication methods, so keep it down on this list. + if (not($borrower) && $rae->{cookies}->{CGISESSID}) { + $borrower = Koha::Auth::Route::Cookie::challenge($rae, $permissions, $authParams); + } + if (not($borrower)) { #HTTP CAS ticket or shibboleth or Persona not implemented + #We don't know how to authenticate, or there is no authentication attempt. + Koha::Exception::LoginFailed->throw(error => "Koha doesn't understand your authentication protocol."); + } + } catch { + if (blessed($_)) { + if ($_->isa('Koha::Exception::LoginFailed') || $_->isa('Koha::Exception::NoPermission')) { + if ($authParams->{authnotrequired}) { #We failed to login, but we can continue anonymously. + $borrower = Koha::Patron->new(); + } + else { + $_->rethrow(); #Anonymous login not allowed this time + } + } + else { + die $_; #Propagate other errors to the calling Controller to redirect as it wants. + } + } + else { + die $_; #Not a Koha::Exception-object + } + }; + + my $session = setUserEnvironment($controller, $rae, $borrower, $authParams); + my $cookie = Koha::Auth::RequestNormalizer::getSessionCookie($controller, $session); + + if ($ENV{KOHA_REST_API_DEBUG} > 2) { + my @cc = caller(0); + print "\n".$cc[3]."\nSESSIONID ".$session->id().", FIRSTNAME ".$session->param('firstname')."\n"; + } + + return ($borrower, $cookie); +} + +=head _authenticate_validateAndNormalizeParameters + +@PARAM1 CGI- or Mojolicious::Controller-object, this is used to identify which web framework to use. +@PARAM2 HASHRef or undef, Permissions HASH telling which Koha permissions the user must have, to access the resource. +@PARAM3 HASHRef or undef, Special authentication parameters, see authenticate() +@THROWS Koha::Exception::BadParameter, if validating parameters fails. +=cut + +sub _authenticate_validateAndNormalizeParameters { + my ($controller, $permissions, $authParams) = @_; + + #Validate $controller. + my $requestAuthElements; + if (blessed($controller) && $controller->isa('CGI')) { + $requestAuthElements = Koha::Auth::RequestNormalizer::normalizeCGI($controller, \@authenticationHeaders, \@authenticationPOSTparams, \@authenticationCookies); + } + elsif (blessed($controller) && $controller->isa('Mojolicious::Controller')) { + $requestAuthElements = Koha::Auth::RequestNormalizer::normalizeMojolicious($controller, \@authenticationHeaders, \@authenticationPOSTparams, \@authenticationCookies); + } + else { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The first parameter MUST be either a 'CGI'-object or a 'Mojolicious::Controller'-object"); + } + #Validate $permissions + unless (not($permissions) || (ref $permissions eq 'HASH')) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The second parameter MUST be 'undef' or a HASHRef of Koha permissions. See C4::Auth::haspermission()."); + } + #Validate $authParams + unless (not($authParams) || (ref $authParams eq 'HASH')) { + Koha::Exception::BadParameter->throw(error => "Koha::Auth::authenticate():> The third parameter MUST be 'undef' or a HASHRef."); + } + + return $requestAuthElements; +} + +=head setUserEnvironment +Set the C4::Context::user_env() and CGI::Session. + +Any idea why there is both the CGI::Session and C4::Context::usernenv?? +=cut + +sub setUserEnvironment { + my ($controller, $rae, $borrower, $authParams) = @_; + + my $session = C4::Auth::get_session( $rae->{cookies}->{CGISESSID} || '' ); + if ($rae->{postParams} && $rae->{postParams}->{koha_login_context} && $rae->{postParams}->{koha_login_context} eq 'REST' && + (not($session->param('koha_login_context')) || $session->param('koha_login_context') ne 'REST') #Make sure we dont create new Sessions for users who want to login many times in a row. + ) { + #We are logging in a user using the REST API, so we need to create a new session context outside of the usual CGISESSID-cookie + $session = C4::Auth::get_session(); + $session->param( 'koha_login_context', $rae->{postParams}->{koha_login_context} ); + } + + C4::Context->_new_userenv( $session->id ); + + _determineUserBranch($rae, $borrower, $authParams, $session); + + #Then start setting remaining session parameters + $session->param( 'number', $borrower->borrowernumber ); + $session->param( 'id', $borrower->userid ); + $session->param( 'cardnumber', $borrower->cardnumber ); + $session->param( 'firstname', $borrower->firstname ); + $session->param( 'surname', $borrower->surname ); + $session->param( 'emailaddress', $borrower->email ); + #originIps contain all the IP's this request has been proxied through. + #Get the last value. This is in line with how the CGI-layer deals with IP-based authentication. + $session->param( 'ip', $rae->{originIps}->[ -1 ] ); + $session->param( 'lasttime', time() ); + $session->flush(); #CGI::Session recommends to flush since auto-flush is not guaranteed. + + #Finally configure the userenv. + 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'), undef, + $session->param('emailaddress'), $session->param('branchprinter'), + $session->param('persona'), $session->param('shibboleth') + ); + + return $session; +} + +sub _determineUserBranch { + my ($rae, $borrower, $authParams, $session) = @_; + + my ($branchcode, $branchname); + if ($rae->{postParams}->{branch}) { + #We are instructed to change the active branch + $branchcode = $rae->{postParams}->{branch}; + } + elsif ($session->param('branch') && $session->param('branch') ne 'NO_LIBRARY_SET') { + ##Branch is already set + $branchcode = $session->param('branch'); + } + elsif ($borrower->branchcode) { + #Default to the borrower's branch + $branchcode = $borrower->branchcode; + } + else { + #No borrower branch? This must be the superuser. + $branchcode = 'NO_LIBRARY_SET'; + $branchname = 'NO_LIBRARY_SET'; + } + unless ($branchname) { + my $library = Koha::Libraries->find($branchcode); + $branchname = $library->branchname if $library; + } + $session->param( 'branch', $branchcode ); + $session->param( 'branchname', ($branchname || 'NO_LIBRARY_SET')); +} + +=head clearUserEnvironment + +Removes the active authentication + +=cut + +sub clearUserEnvironment { + my ($sessionid, $authParams) = @_; + + my $session; + unless (blessed($sessionid)) { + $session = C4::Auth::get_session( $sessionid ); + } + else { + $session = $sessionid; + } + + if (C4::Context->userenv()) { + C4::Context::_unset_userenv( $session->id ); + } + $session->delete(); + $session->flush(); +} + +1; diff --git a/Koha/Auth/Challenge.pm b/Koha/Auth/Challenge.pm new file mode 100644 index 0000000..02c45f1 --- /dev/null +++ b/Koha/Auth/Challenge.pm @@ -0,0 +1,74 @@ +package Koha::Auth::Challenge; + +# Copyright 2015 Vaara-kirjastot +# +# 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; + +=head1 NAME Koha::Auth::Challenge + +=head2 SYNOPSIS + +This is a authentication challenge parent class. +All Challenge-objects must implement the challenge()-method. + +=head SUBLASSING + +package Koha::Auth::Challenge::YetAnotherChallenge; + +use base qw('Koha::Auth::Challenge'); + +sub challenge { + #Implement the parent method to make this subclass interoperable. +} + +=head2 USAGE + + use Scalar::Util qw(blessed); + try { + ... + Koha::Auth::Challenge::Version::challenge(); + Koha::Auth::Challenge::OPACMaintenance::challenge(); + Koha::Auth::Challenge::YetAnotherChallenge::challenge(); + ... + } catch { + if (blessed($_)) { + if ($_->isa('Koha::Exception::VersionMismatch')) { + ##handle exception + } + elsif ($_->isa('Koha::Exception::AnotherKindOfException')) { + ... + } + ... + else { + warn "Unknown exception class ".ref($_)."\n"; + die $_; #Unhandled exception case + } + } + else { + die $_; #Not a Koha::Exception-object + } + }; + +=cut + +sub challenge { + #@OVERLOAD this "interface" + warn caller()." doesn't implement challenge()\n"; +} + +1; diff --git a/Koha/Auth/Challenge/Cookie.pm b/Koha/Auth/Challenge/Cookie.pm new file mode 100644 index 0000000..e8d98c7 --- /dev/null +++ b/Koha/Auth/Challenge/Cookie.pm @@ -0,0 +1,88 @@ +package Koha::Auth::Challenge::Cookie; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Context; +use C4::Auth; +use Koha::AuthUtils; +use Koha::Patrons; + +use Koha::Exception::LoginFailed; + +use base qw(Koha::Auth::Challenge); + +=head challenge +STATIC + + Koha::Auth::Challenge::Cookie::challenge($cookieValue); + +Checks if the given authentication cookie value matches a session, and checks if +the session is still active. +@PARAM1 String, hashed session key identifying a session in koha.sessions +@RETURNS Koha::Patron matching the verified and active session +@THROWS Koha::Exception::LoginFailed, if no session is found, + if the session has expired, + if the session IP address changes, + if no borrower was found for the session +=cut + +sub challenge { + my ($cookie, $originIps) = @_; + + my $session = C4::Auth::get_session($cookie); + Koha::Exception::LoginFailed->throw(error => "No session matching the given session identifier '$session'.") unless $session; + + # See if the given session is timed out + if (isSessionExpired($session)) { + Koha::Auth::clearUserEnvironment($session, {}); + Koha::Exception::LoginFailed->throw(error => "Session expired, please login again."); + } + # Check if we still access using the same IP than when the session was initialized. + elsif ( C4::Context->preference('SessionRestrictionByIP')) { + + my $sameIpFound = grep {$session->param('ip') eq $_} @$originIps; + + unless ($sameIpFound) { + Koha::Auth::clearUserEnvironment($session, {}); + Koha::Exception::LoginFailed->throw(error => "Session's client address changed, please login again."); + } + } + + #Get the Borrower-object + my $userid = $session->param('id'); + my $borrower = Koha::AuthUtils::checkKohaSuperuserFromUserid($userid); + $borrower = Koha::Patrons->find({userid => $userid}) if not($borrower) && $userid; + Koha::Exception::LoginFailed->throw(error => "Cookie authentication succeeded, but no borrower found with userid '".($userid || '')."'.") + unless $borrower; + + $session->param( 'lasttime', time() ); + return $borrower; +} + +sub isSessionExpired { + my ($session) = @_; + + if ( ($session->param('lasttime') || 0) < (time()- C4::Auth::_timeout_syspref()) ) { + return 1; + } + return 0; +} + +1; diff --git a/Koha/Auth/Challenge/IndependentBranchesAutolocation.pm b/Koha/Auth/Challenge/IndependentBranchesAutolocation.pm new file mode 100644 index 0000000..15821f4 --- /dev/null +++ b/Koha/Auth/Challenge/IndependentBranchesAutolocation.pm @@ -0,0 +1,53 @@ +package Koha::Auth::Challenge::IndependentBranchesAutolocation; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Context; + +use Koha::Libraries; + +use Koha::Exception::LoginFailed; + +use base qw(Koha::Auth::Challenge); + +=head challenge + +If sysprefs 'IndependentBranches' and 'Autolocation' are active, checks if the user +is in the correct network region to login. +@PARAM1 String, branchcode of the branch the current user is authenticating in to. +@THROWS Koha::Exception::LoginFailed, if the user is in the wrong network segment. +=cut + +sub challenge { + my ($currentBranchcode) = @_; + + if ( $currentBranchcode && C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) { + my $ip = $ENV{'REMOTE_ADDR'}; + + my $branches = Koha::Libraries->search->unblessed; + # we have to check they are coming from the right ip range + my $domain = $branches->{$currentBranchcode}->{'branchip'}; + if ( $ip !~ /^$domain/ ) { + Koha::Exception::LoginFailed->throw(error => "Branch '$currentBranchcode' is inaccessible from this network."); + } + } +} + +1; diff --git a/Koha/Auth/Challenge/OPACMaintenance.pm b/Koha/Auth/Challenge/OPACMaintenance.pm new file mode 100644 index 0000000..8490379 --- /dev/null +++ b/Koha/Auth/Challenge/OPACMaintenance.pm @@ -0,0 +1,44 @@ +package Koha::Auth::Challenge::OPACMaintenance; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Context; + +use base qw(Koha::Auth::Challenge); + +use Koha::Exception::ServiceTemporarilyUnavailable; + +=head challenge +STATIC + + Koha::Auth::Challenge::OPACMaintenance::challenge(); + +Checks if OPAC is under maintenance. + +@THROWS Koha::Exception::ServiceTemporarilyUnavailable +=cut + +sub challenge { + if ( C4::Context->preference('OpacMaintenance') ) { + Koha::Exception::ServiceTemporarilyUnavailable->throw(error => 'OPAC is under maintenance'); + } +} + +1; diff --git a/Koha/Auth/Challenge/Password.pm b/Koha/Auth/Challenge/Password.pm new file mode 100644 index 0000000..41dd405 --- /dev/null +++ b/Koha/Auth/Challenge/Password.pm @@ -0,0 +1,127 @@ +package Koha::Auth::Challenge::Password; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Patrons; +use Koha::AuthUtils; + +use base qw(Koha::Auth::Challenge); + +use Koha::Exception::LoginFailed; + +our @usernameAliasColumns = ('userid', 'cardnumber'); #Possible columns to treat as the username when authenticating. Must be UNIQUE in DB. + +=head NAME Koha::Auth::Challenge::Password + +=head SYNOPSIS + +This module implements the more specific behaviour of the password authentication component. + +=cut + +=head challenge +STATIC + + Koha::Auth::Challenge::Password::challenge(); + +@RETURN Koha::Patron-object if check succeedes, otherwise throws exceptions. +@THROWS Koha::Exception::LoginFailed from Koha::AuthUtils password checks. +=cut + +sub challenge { + my ($userid, $password) = @_; + + my $borrower; + if (C4::Context->config('useldapserver')) { + $borrower = Koha::Auth::Challenge::Password::checkLDAPPassword($userid, $password); + return $borrower if $borrower; + } + if (C4::Context->preference('casAuthentication')) { + warn("Koha::Auth doesn't support CAS-authentication yet. Please refactor the CAS client implementation to work with Koha::Auth. It cant be too hard :)"); + } + if (C4::Context->config('useshibboleth')) { + warn("Koha::Auth doesn't support Shibboleth-authentication yet. Please refactor the Shibboleth client implementation to work with Koha::Auth. It cant be too hard :)"); + } + + return Koha::Auth::Challenge::Password::checkKohaPassword($userid, $password); +} + +=head checkKohaPassword + + my $borrower = Koha::Auth::Challenge::Password::checkKohaPassword($userid, $password); + +Checks if the given username and password match anybody in the Koha DB +@PARAM1 String, user identifier, either the koha.borrowers.userid, or koha.borrowers.cardnumber +@PARAM2 String, clear text password from the authenticating user +@RETURN Koha::Patron, if login succeeded. + Sets Koha::Patron->isSuperuser() if the user is a superuser. +@THROWS Koha::Exception::LoginFailed, if no matching password was found for all username aliases in Koha. +=cut + +sub checkKohaPassword { + my ($userid, $password) = @_; + my $borrower; #Find the borrower to return + + $borrower = Koha::AuthUtils::checkKohaSuperuser($userid, $password); + return $borrower if $borrower; + + my $usernameFound = 0; #Report to the user if userid/barcode was found, even if the login failed. + #Check for each username alias if we can confirm a login with that. + for my $unameAlias (@usernameAliasColumns) { + my $borrower = Koha::Patrons->find({$unameAlias => $userid}); + if ( $borrower ) { + $usernameFound = 1; + return $borrower if ( Koha::AuthUtils::checkHash( $password, $borrower->password ) ); + } + } + + Koha::Exception::LoginFailed->throw(error => "Password authentication failed for the given ".( ($usernameFound) ? "password" : "username and password")."."); +} + +=head checkLDAPPassword + +Checks if the given username and password match anybody in the LDAP service +@PARAM1 String, user identifier +@PARAM2 String, clear text password from the authenticating user +@RETURN Koha::Patron, or + undef if we couldn't reliably contact the LDAP server so we should + fallback to local Koha Password authentication. +@THROWS Koha::Exception::LoginFailed, if LDAP login failed +=cut + +sub checkLDAPPassword { + my ($userid, $password) = @_; + + #Lazy load dependencies because somebody might never need them. + require C4::Auth_with_ldap; + + my ($retval, $cardnumber, $local_userid) = C4::Auth_with_ldap::checkpw_ldap($userid, $password); # EXTERNAL AUTH + if ($retval == -1) { + Koha::Exception::LoginFailed->throw(error => "LDAP authentication failed for the given username and password"); + } + + if ($retval) { + my $borrower = Koha::Patrons->find({userid => $local_userid}); + return $borrower; + } + return undef; +} + +1; diff --git a/Koha/Auth/Challenge/Permission.pm b/Koha/Auth/Challenge/Permission.pm new file mode 100644 index 0000000..f284919 --- /dev/null +++ b/Koha/Auth/Challenge/Permission.pm @@ -0,0 +1,42 @@ +package Koha::Auth::Challenge::Permission; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Auth::PermissionManager; + +use base qw(Koha::Auth::Challenge); + +=head challenge +STATIC + + Koha::Auth::Challenge::Permission::challenge($borrower, $permissionsRequired); + +@THROWS Koha::Exception::NoPermission with the missing permission if permissions + are inadequate +=cut + +sub challenge { + my ($borrower, $permissionsRequired) = @_; + + my $permissionManager = Koha::Auth::PermissionManager->new(); + $permissionManager->hasPermissions($borrower, $permissionsRequired); +} + +1; diff --git a/Koha/Auth/Challenge/RESTV1.pm b/Koha/Auth/Challenge/RESTV1.pm new file mode 100644 index 0000000..c0cf089 --- /dev/null +++ b/Koha/Auth/Challenge/RESTV1.pm @@ -0,0 +1,179 @@ +package Koha::Auth::Challenge::RESTV1; + +# Copyright 2015 Vaara-kirjastot +# +# 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 DateTime::Format::HTTP; +use DateTime; + +use Koha::Patrons; + +use base qw(Koha::Auth::Challenge); + +use Koha::Exception::LoginFailed; +use Koha::Exception::BadParameter; +use Koha::Exception::Parse; + +=head challenge + + my $borrower = Koha::Auth::Challenge::RESTV1::challenge(); + +For authentication to succeed, the client have to send 2 HTTP +headers: + - X-Koha-Date: the standard HTTP Date header complying to RFC 1123, simply wrapped to X-Koha-Date, + since the w3-specification forbids setting the Date-header from javascript. + - Authorization: the standard HTTP Authorization header, see below for how it is constructed. + +=head2 HTTP Request example + +GET /api/v1/borrowers/12 HTTP/1.1 +Host: api.yourkohadomain.fi +X-Koha-Date: Mon, 26 Mar 2007 19:37:58 +0000 +Authorization: Koha admin69:frJIUN8DYpKDtOLCwo//yllqDzg= + +=head2 Constructing the Authorization header + +-You brand the authorization header with "Koha" +-Then you give the userid/cardnumber of the user authenticating. +-Then the hashed signature. + +The signature is a HMAC-SHA256-HEX hash of several elements of the request, +separated by spaces: + - HTTP method (uppercase) + - userid/cardnumber + - X-Koha-Date-header +Signed with the Borrowers API key + +The server then tries to rebuild the signature with each of the user's API keys. +If one matches the received signature, then authentication is almost OK. + +To avoid requests to be replayed, the last request's X-Koha-Date-header is stored +in database and the authentication succeeds only if the stored Date +is lesser than the X-Koha-Date-header. + +=head2 Constructing the signature example + +Signature = HMAC-SHA256-HEX("HTTPS" + " " + + "/api/v1/borrowers/12?howdoyoudo=voodoo" + " " + + "admin69" + " " + + "760818212" + " " + + "frJIUN8DYpKDtOLCwo//yllqDzg=" + ); + +=head + +@PARAM1 HASHRef of Header name => values +@PARAM2 String, upper case request method name, eg. HTTP or HTTPS +@PARAM3 String the request uri +@RETURNS Koha::Patron if authentication succeeded. +@THROWS Koha::Exception::LoginFailed, if API key signature verification failed +@THROWS Koha::Exception::BadParameter +@THROWS Koha::Exception::UnknownObject, if we cannot find a Borrower with the given input. +=cut + +sub challenge { + my ($headers, $method, $uri) = @_; + + my $req_dt; + eval { + $req_dt = DateTime::Format::HTTP->parse_datetime( $headers->{'X-Koha-Date'} ); #Returns DateTime + }; + if (not($req_dt) || $@) { + Koha::Exception::BadParameter->throw(error => "X-Koha-Date HTTP-header [".$headers->{'X-Koha-Date'}."] is not well formed. It needs to be of RFC 1123 -date format, eg. 'X-Koha-Date: Wed, 09 Feb 1994 22:23:32 +0200'"); + } + + my $authorizationHeader = $headers->{'Authorization'}; + my ($req_username, $req_signature); + if ($authorizationHeader =~ /^Koha (\S+?):(\w+)$/) { + $req_username = $1; + $req_signature = $2; + } + else { + Koha::Exception::BadParameter->throw(error => "Authorization HTTP-header is not well formed. It needs to be of format 'Authorization: Koha userid:signature'"); + } + + my $borrower = Koha::Patrons->cast($req_username); + + my @apikeys = Koha::ApiKeys->search({ + borrowernumber => $borrower->borrowernumber, + active => 1, + }); + Koha::Exception::LoginFailed->throw(error => "User has no API keys. Please add one using the Staff interface or OPAC.") unless @apikeys; + + my $matchingApiKey; + foreach my $apikey (@apikeys) { + my $signature = makeSignature($method, $req_username, $headers->{'X-Koha-Date'}, $apikey); + + if ($signature eq $req_signature) { + $matchingApiKey = $apikey; + last(); + } + } + + unless ($matchingApiKey) { + Koha::Exception::LoginFailed->throw(error => "API key authentication failed."); + } + + #Checking for message replay abuses or change control using ETAG shouldn't be done here, since we need to make valid request more often than every second. + #unless ($matchingApiKey->last_request_time < $req_dt->epoch()) { + # Koha::Exception::BadParameter->throw(error => "X-Koha-Date HTTP-header is stale, expected later date than '".DateTime::Format::HTTP->format_datetime($req_dt)."'"); + #} + + $matchingApiKey->set({last_request_time => $req_dt->epoch()}); + $matchingApiKey->store(); + + return $borrower; +} + +sub makeSignature { + my ($method, $userid, $headerXKohaDate, $apiKey) = @_; + + my $message = join(' ', uc($method), $userid, $headerXKohaDate); + my $digest = Digest::SHA::hmac_sha256_hex($message, $apiKey->api_key); + + if ($ENV{KOHA_REST_API_DEBUG} > 2) { + my @cc = caller(1); + print "\n".$cc[3]."\nMAKESIGNATURE $method, $userid, $headerXKohaDate, ".$apiKey->api_key.", DIGEST $digest\n"; + } + + return $digest; +} + +=head prepareAuthenticationHeaders +@PARAM1 Koha::Patron, to authenticate +@PARAM2 DateTime, OPTIONAL, the timestamp of the HTTP request +@PARAM3 HTTP verb, 'get', 'post', 'patch', 'put', ... +@RETURNS HASHRef of authentication HTTP header names and their values. { + "X-Koha-Date" => "Mon, 26 Mar 2007 19:37:58 +0000", + "Authorization" => "Koha admin69:frJIUN8DYpKDtOLCwo//yllqDzg=", + } +=cut + +sub prepareAuthenticationHeaders { + my ($borrower, $dateTime, $method) = @_; + $borrower = Koha::Patrons->cast($borrower); + + my $headerXKohaDate = DateTime::Format::HTTP->format_datetime( + ($dateTime || DateTime->now( time_zone => C4::Context->tz() )) + ); + my $headerAuthorization = "Koha ".$borrower->userid.":".makeSignature($method, $borrower->userid, $headerXKohaDate, $borrower->getApiKey('active')); + return {'X-Koha-Date' => $headerXKohaDate, + 'Authorization' => $headerAuthorization}; +} + +1; diff --git a/Koha/Auth/Challenge/Version.pm b/Koha/Auth/Challenge/Version.pm new file mode 100644 index 0000000..94b2d7a --- /dev/null +++ b/Koha/Auth/Challenge/Version.pm @@ -0,0 +1,56 @@ +package Koha::Auth::Challenge::Version; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Context; +use Koha; + +use base qw(Koha::Auth::Challenge); + +use Koha::Exception::VersionMismatch; +use Koha::Exception::BadSystemPreference; + +=head challenge +STATIC + + Koha::Auth::Challenge::Version::challenge(); + +Checks if the DB version is valid. + +@THROWS Koha::Exception::VersionMismatch, if versions do not match +@THROWS Koha::Exception::BadSystemPreference, if "Version"-syspref is not set. + This probably means that Koha has not been installed yet. +=cut + +sub challenge { + my $versionSyspref = C4::Context->preference('Version'); + unless ( $versionSyspref ) { + Koha::Exception::BadSystemPreference->throw(error => "No Koha 'Version'-system preference defined. Koha needs to be installed."); + } + + my $kohaversion = Koha::version(); + # remove the 3 last . to have a Perl number + $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; + if ( $versionSyspref < $kohaversion ) { + Koha::Exception::VersionMismatch->throw(error => "Database update needed. Database is 'v$versionSyspref' and Koha is 'v$kohaversion'"); + } +} + +1; diff --git a/Koha/Auth/RequestNormalizer.pm b/Koha/Auth/RequestNormalizer.pm new file mode 100644 index 0000000..2fe65e7 --- /dev/null +++ b/Koha/Auth/RequestNormalizer.pm @@ -0,0 +1,178 @@ +package Koha::Auth::RequestNormalizer; + +# Copyright 2015 Vaara-kirjastot +# +# 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 Scalar::Util qw(blessed); + + +=head normalizeCGI + +Takes a CGI-object and finds the authentication markers from it. +@PARAM1 CGI-object. +@PARAM2 ARRAYRef, authentication headers that should be extracted for authentication +@PARAM3 ARRAYRef, authentication POST parameters that should be extracted for authentication +@PARAM4 ARRAYRef, authentication cookies that should be extracted for authentication +@RETURNS List of : HASHRef of headers required for authentication, or undef + HASHRef of POST parameters required for authentication, or undef + HASHRef of the authenticaton cookie name => value, or undef +=cut + +sub normalizeCGI { + my ($controller, $authenticationHeaders, $authenticationPOSTparams, $authenticationCookies) = @_; + + my ($headers, $postParams, $cookies) = ({}, {}, {}); + foreach my $authHeader (@$authenticationHeaders) { + if (my $val = $controller->http($authHeader)) { + $headers->{$authHeader} = $val; + } + } + foreach my $authParam (@$authenticationPOSTparams) { + if (my $val = $controller->param($authParam)) { + $postParams->{$authParam} = $val; + } + } + foreach my $authCookie (@$authenticationCookies) { + if (my $val = $controller->cookie($authCookie)) { + $cookies->{$authCookie} = $val; + } + } + + my $method = $1 if ($ENV{SERVER_PROTOCOL} =~ /^(.+?)\//); + + my @originIps = ($ENV{'REMOTE_ADDR'}); + + my $requestAuthElements = { #Collect the authentication elements here. + headers => $headers, + postParams => $postParams, + cookies => $cookies, + originIps => \@originIps, + method => $method, + url => $ENV{REQUEST_URI}, + }; + return $requestAuthElements; +} + +=head normalizeMojolicious + +Takes a Mojolicious::Controller-object and finds the authentication markers from it. +@PARAM1 Mojolicious::Controller-object. +@PARAM2-4 See normalizeCGI() +@RETURNS HASHRef of the request's authentication elements marked for extraction, eg: + { + headers => { X-Koha-Signature => '32rFrFw3iojsev34AS', + X-Koha-Username => 'pavlov'}, + POSTparams => { password => '1234', + userid => 'pavlov'}, + cookies => { CGISESSID => '233FADFEV3as1asS' }, + method => 'https', + url => '/borrower/12/holds' + } +=cut + +sub normalizeMojolicious { + my ($controller, $authenticationHeaders, $authenticationPOSTparams, $authenticationCookies) = @_; + + my $request = $controller->req(); + my ($headers, $postParams, $cookies) = ({}, {}, {}); + my $headersHash = $request->headers()->to_hash(); + foreach my $authHeader (@$authenticationHeaders) { + if (my $val = $headersHash->{$authHeader}) { + $headers->{$authHeader} = $val; + } + } + foreach my $authParam (@$authenticationPOSTparams) { + if (my $val = $request->param($authParam)) { + $postParams->{$authParam} = $val; + } + } + + my $requestCookies = $request->cookies; + if (scalar(@$requestCookies)) { + foreach my $authCookieName (@$authenticationCookies) { + foreach my $requestCookie (@$requestCookies) { + if ($authCookieName eq $requestCookie->name) { + $cookies->{$authCookieName} = $requestCookie->value; + } + } + } + } + + my @originIps = ($controller->tx->original_remote_address()); + push @originIps, $request->headers()->header('X-Forwarded-For') if $request->headers()->header('X-Forwarded-For'); + + my $requestAuthElements = { #Collect the authentication elements here. + headers => $headers, + postParams => $postParams, + cookies => $cookies, + originIps => \@originIps, + method => $controller->req->method, + url => '/' . $controller->req->url->path_query, + }; + return $requestAuthElements; +} + +=head getSessionCookie + +@PARAM1 CGI- or Mojolicious::Controller-object, this is used to identify which web framework to use. +@PARAM2 CGI::Session. +@RETURNS a Mojolicious cookie or a CGI::Cookie. +=cut + +sub getSessionCookie { + my ($controller, $session) = @_; + + my $cookie = { + name => 'CGISESSID', + value => $session->id, + }; + my $cookieOk; + + if (blessed($controller)) { + if ($controller->isa('CGI')) { + $cookie->{HttpOnly} = 1; + $cookieOk = $controller->cookie( $cookie ); + } + elsif ($controller->isa('Mojolicious::Controller')) { + my $cooksreq = $controller->req->cookies; + my $cooksres = $controller->res->cookies; + foreach my $c (@{$controller->res->cookies}) { + + if ($c->name eq 'CGISESSID') { + $c->value($cookie->{value}); + $cookieOk = $c; + } + } + } + } + #No auth cookie, so we must make one :) + unless ($cookieOk) { + $controller->res->cookies($cookie); + my $cooks = $controller->res->cookies(); + foreach my $c (@$cooks) { + if ($c->name eq 'CGISESSID') { + $cookieOk = $c; + last; + } + } + } + return $cookieOk; +} + +1; \ No newline at end of file diff --git a/Koha/Auth/Route.pm b/Koha/Auth/Route.pm new file mode 100644 index 0000000..d763fa8 --- /dev/null +++ b/Koha/Auth/Route.pm @@ -0,0 +1,75 @@ +package Koha::Auth::Route; + +use Modern::Perl; + +# Copyright 2015 Vaara-kirjastot +# +# 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 . + +=head + +=NAME Koha::Auth::Route + +=SYNOPSIS + +This is an interface definition for Koha::Auth::Route::* -subclasses. +This documentation explains how to subclass different routes. + +=USAGE + + if ($userid && $password) { + $borrower = Koha::Auth::Route::::challenge($requestAuthElements, $permissionsRequired, $routeParams); + } + +=head INPUT + +Each Route gets three parameters: + $requestAuthElements, HASHRef of HASHRefs: + headers => HASHRef of HTTP Headers matching the @authenticationHeaders-package + variable in Koha::Auth, + Eg. { 'X-Koha-Signature' => "23in4ow2gas2opcnpa", ... } + postParams => HASHRef of HTTP POST parameters matching the + @authenticationPOSTparams-package variable in Koha::Auth, + Eg. { password => '1234', 'userid' => 'admin'} + cookies => HASHRef of HTTP Cookies matching the + @authenticationPOSTparams-package variable in Koha::Auth, + EG. { CGISESSID => '9821rj1kn3tr9ff2of2ln1' } + $permissionsRequired: + HASHRef of Koha permissions. + See Koha::Auth::PermissionManager for example. + $routeParams: HASHRef of special Route-related data + {inOPAC => 1, authnotrequired => 0, ...} + +=head OUTPUT + +Each route must return a Koha::Patron-object representing the authenticated user. +Even if the login succeeds with a superuser or similar virtual user, like +anonymous login, a mock Borrower-object must be returned. +If the login fails, each route must throw Koha::Exceptions to notify the cause +of the failure. + +=head ROUTE STRUCTURE + +Each route consists of Koha::Auth::Challenge::*-objects to test for various +authentication challenges. + +See. Koha::Auth::Challenge for more information. + +=cut + +sub challenge {}; #@OVERLOAD this "interface" + +1; diff --git a/Koha/Auth/Route/Cookie.pm b/Koha/Auth/Route/Cookie.pm new file mode 100644 index 0000000..9637a5b --- /dev/null +++ b/Koha/Auth/Route/Cookie.pm @@ -0,0 +1,44 @@ +package Koha::Auth::Route::Cookie; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Auth::Challenge::OPACMaintenance; +use Koha::Auth::Challenge::Version; +use Koha::Auth::Challenge::Cookie; +use Koha::Auth::Challenge::Permission; + +use base qw(Koha::Auth::Route); + +=head challenge +See Koha::Auth::Route, for usage documentation. +@THROWS Koha::Exceptions from authentication components. +=cut + +sub challenge { + my ($rae, $permissionsRequired, $routeParams) = @_; + + Koha::Auth::Challenge::OPACMaintenance::challenge() if $routeParams->{inOPAC}; + Koha::Auth::Challenge::Version::challenge(); + my $borrower = Koha::Auth::Challenge::Cookie::challenge($rae->{cookies}->{CGISESSID}, $rae->{originIps}); + Koha::Auth::Challenge::Permission::challenge($borrower, $permissionsRequired) if $permissionsRequired; + return $borrower; +} + +1; diff --git a/Koha/Auth/Route/Password.pm b/Koha/Auth/Route/Password.pm new file mode 100644 index 0000000..daa7cbf --- /dev/null +++ b/Koha/Auth/Route/Password.pm @@ -0,0 +1,46 @@ +package Koha::Auth::Route::Password; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Auth::Challenge::OPACMaintenance; +use Koha::Auth::Challenge::Version; +use Koha::Auth::Challenge::IndependentBranchesAutolocation; +use Koha::Auth::Challenge::Password; +use Koha::Auth::Challenge::Permission; + +use base qw(Koha::Auth::Route); + +=head challenge +See Koha::Auth::Route, for usage documentation. +@THROWS Koha::Exceptions from authentication components. +=cut + +sub challenge { + my ($rae, $permissionsRequired, $routeParams) = @_; + + Koha::Auth::Challenge::OPACMaintenance::challenge() if $routeParams->{inOPAC}; + Koha::Auth::Challenge::Version::challenge(); + Koha::Auth::Challenge::IndependentBranchesAutolocation::challenge($routeParams->{branch}); + my $borrower = Koha::Auth::Challenge::Password::challenge($rae->{postParams}->{userid} || $rae->{postParams}->{cardnumber}, $rae->{postParams}->{password}); + Koha::Auth::Challenge::Permission::challenge($borrower, $permissionsRequired) if $permissionsRequired; + return $borrower; +} + +1; diff --git a/Koha/Auth/Route/RESTV1.pm b/Koha/Auth/Route/RESTV1.pm new file mode 100644 index 0000000..64ac84f --- /dev/null +++ b/Koha/Auth/Route/RESTV1.pm @@ -0,0 +1,43 @@ +package Koha::Auth::Route::RESTV1; + +# Copyright 2015 Vaara-kirjastot +# +# 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::Auth::Challenge::Version; +use Koha::Auth::Challenge::RESTV1; +use Koha::Auth::Challenge::Permission; + +use base qw(Koha::Auth::Route); + +=head challenge +See Koha::Auth::Route, for usage documentation. +@THROWS Koha::Exceptions from authentication components. +=cut + +sub challenge { + my ($rae, $permissionsRequired, $routeParams) = @_; + + #Koha::Auth::Challenge::RESTMaintenance::challenge() if $routeParams->{inREST}; #NOT IMPLEMENTED YET + Koha::Auth::Challenge::Version::challenge(); + my $borrower = Koha::Auth::Challenge::RESTV1::challenge($rae->{headers}, $rae->{method}, $rae->{url}); + Koha::Auth::Challenge::Permission::challenge($borrower, $permissionsRequired) if $permissionsRequired; + return $borrower; +} + +1; diff --git a/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt b/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt index c6a050c..1c6430e 100644 --- a/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt +++ b/koha-tmpl/intranet-tmpl/prog/en/modules/auth.tt @@ -45,7 +45,9 @@
[% FOREACH INPUT IN INPUTS %] - + [% UNLESS INPUT.name == 'logout.x' #No reason to send the logout-signal again %] + + [% END %] [% END %]

diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt index 6d941bf..a0a04b0 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt +++ b/koha-tmpl/opac-tmpl/bootstrap/en/modules/opac-auth.tt @@ -154,7 +154,9 @@

[% FOREACH INPUT IN INPUTS %] - + [% UNLESS INPUT.name == 'logout.x' #No reason to send the logout-signal again %] + + [% END %] [% END %] diff --git a/opac/opac-search-history.pl b/opac/opac-search-history.pl index 34b0e84..731d72b 100755 --- a/opac/opac-search-history.pl +++ b/opac/opac-search-history.pl @@ -41,7 +41,6 @@ my ($template, $loggedinuser, $cookie) = get_template_and_user( query => $cgi, type => "opac", authnotrequired => 1, - flagsrequired => {borrowers => 1}, debug => 1, } ); diff --git a/opac/opac-user.pl b/opac/opac-user.pl index 278c04b..08f664c 100755 --- a/opac/opac-user.pl +++ b/opac/opac-user.pl @@ -293,7 +293,7 @@ $template->param( ); # current alert subscriptions -my $alerts = getalert($borrowernumber) if $borrowernumber; +my $alerts = getalert($borrowernumber) if $borrowernumber; #Superuser has no borrowernumber foreach ( @$alerts ) { $_->{ $_->{type} } = 1; $_->{relatedto} = findrelatedto( $_->{type}, $_->{externalid} ); diff --git a/t/db_dependent/Koha/Borrower.t b/t/db_dependent/Koha/Borrower.t new file mode 100644 index 0000000..6a7e123 --- /dev/null +++ b/t/db_dependent/Koha/Borrower.t @@ -0,0 +1,55 @@ +#!/usr/bin/perl + +# Copyright 2015 Open Source Freedom Fighters +# +# 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 Test::More; #Please don't set the test count here. It is nothing but trouble when rebasing against master and is of dubious help. + +use Koha::Patron; + + + +testIsSuperuser(); + + + + + +################################################################################ +#### Define test subroutines here ############################################## +################################################################################ + +=head testIsSuperuser +@UNIT_TEST +Tests Koha::Borrower->isSuperuser() +=cut + +sub testIsSuperuser { + my $borrower = Koha::Patron->new(); + ok((not(defined($borrower->isSuperuser()))), "isSuperuser(): By default user is not defined as superuser."); + ok(($borrower->isSuperuser(1) == 1), "isSuperuser(): Setting user as superuser returns 1."); + ok(($borrower->isSuperuser() == 1), "isSuperuser(): Getting superuser status from a superuser returns 1."); + ok((not(defined($borrower->isSuperuser(0)))), "isSuperuser(): Removing superuser status from a superuser OK and returns undef"); + ok((not(defined($borrower->isSuperuser()))), "isSuperuser(): Ex-superuser superuser status is undef"); +} + + + + +####################### +done_testing(); #YAY!! +####################### -- 2.7.4