Bugzilla – Attachment 40723 Details for
Bug 7174
Authentication rewriting
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Help
|
New Account
|
Log In
[x]
|
Forgot Password
Login:
[x]
[patch]
Bug 7174 - Authentication rewriting - Things are getting more serious. And problematic. Deprecating lots of stuff. Encapsulating authentication behaviour into nice reusable modules. Wondering about all the tiny flags and conditionals. Creating the first a
Bug-7174---Authentication-rewriting---Things-are-g.patch (text/plain), 41.55 KB, created by
Olli-Antti Kivilahti
on 2015-06-29 16:10:31 UTC
(
hide
)
Description:
Bug 7174 - Authentication rewriting - Things are getting more serious. And problematic. Deprecating lots of stuff. Encapsulating authentication behaviour into nice reusable modules. Wondering about all the tiny flags and conditionals. Creating the first a
Filename:
MIME Type:
Creator:
Olli-Antti Kivilahti
Created:
2015-06-29 16:10:31 UTC
Size:
41.55 KB
patch
obsolete
>From 2122005478a3b0ae8ce599f401facf8439b3fbec Mon Sep 17 00:00:00 2001 >From: Olli-Antti Kivilahti <olli-antti.kivilahti@jns.fi> >Date: Fri, 26 Jun 2015 17:17:03 +0300 >Subject: [PATCH] Bug 7174 - Authentication rewriting - Things are getting > more serious. And problematic. Deprecating lots of stuff. > Encapsulating authentication behaviour into nice reusable > modules. Wondering about all the tiny flags and > conditionals. Creating the first authentication route. See > Koha::Auth::Route for "interface" definition. > >--- > C4/Auth.pm | 179 +++++++++++++++++++++++++++++-------- > C4/Auth_with_ldap.pm | 11 ++- > Koha/Auth.pm | 187 +++++++++++++++++++++++++++++++++++++++ > Koha/Auth/Component.pm | 153 ++++++++++++++++++++++++++++++++ > Koha/Auth/Route.pm | 65 ++++++++++++++ > Koha/Auth/Route/Password.pm | 39 ++++++++ > Koha/AuthUtils.pm | 151 +++++++++++++++++++++++++++++++ > Koha/Borrower.pm | 26 ++++++ > t/db_dependent/Auth_with_ldap.t | 18 ++-- > t/db_dependent/Koha/Borrower.t | 55 ++++++++++++ > 10 files changed, 835 insertions(+), 49 deletions(-) > create mode 100644 Koha/Auth.pm > create mode 100644 Koha/Auth/Component.pm > create mode 100644 Koha/Auth/Route.pm > create mode 100644 Koha/Auth/Route/Password.pm > create mode 100644 t/db_dependent/Koha/Borrower.t > >diff --git a/C4/Auth.pm b/C4/Auth.pm >index 280db6a..fa45209 100644 >--- a/C4/Auth.pm >+++ b/C4/Auth.pm >@@ -19,11 +19,12 @@ package C4::Auth; > > use strict; > use warnings; >-use Digest::MD5 qw(md5_base64); >+use Digest::MD5 qw(md5_base64); #@DEPRECATED Digest::MD5, don't use it or you will get hurt. > use JSON qw/encode_json/; > use URI::Escape; > use CGI::Session; > use Scalar::Util qw(blessed); >+use Try::Tiny; > > require Exporter; > use C4::Context; >@@ -34,6 +35,8 @@ use C4::Search::History; > use Koha; > use Koha::Borrowers; > use Koha::AuthUtils qw(hash_password); >+use Koha::Auth; >+use Koha::Auth::Component; > use POSIX qw/strftime/; > use List::MoreUtils qw/ any /; > use Encode qw( encode is_utf8); >@@ -639,50 +642,63 @@ has authenticated. > > =cut > >+=head _version_check >+#@DEPRECATED See Bug 7174 >+use Koha::Auth::Component::* instead >+=cut >+ > sub _version_check { > my $type = shift; > my $query = shift; > my $version; > >- # If version syspref is unavailable, it means Koha is being installed, >- # and so we must redirect to OPAC maintenance page or to the WebInstaller >- # also, if OpacMaintenance is ON, OPAC should redirect to maintenance >- if ( C4::Context->preference('OpacMaintenance') && $type eq 'opac' ) { >- warn "OPAC Install required, redirecting to maintenance"; >- print $query->redirect("/cgi-bin/koha/maintenance.pl"); >- safe_exit; >- } >- unless ( $version = C4::Context->preference('Version') ) { # assignment, not comparison >- if ( $type ne 'opac' ) { >- warn "Install required, redirecting to Installer"; >- print $query->redirect("/cgi-bin/koha/installer/install.pl"); >- } else { >- warn "OPAC Install required, redirecting to maintenance"; >- print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ try { >+ Koha::Auth::Component::checkOPACMaintenance() if ( $type eq 'opac' ); >+ Koha::Auth::Component::checkVersion(); >+ } catch { >+ if (blessed($_)) { >+ if ($_->isa('Koha::Exception::VersionMismatch')) { >+ # check that database and koha version are the same >+ # there is no DB version, it's a fresh install, >+ # go to web installer >+ # there is a DB version, compare it to the code version >+ my $warning = $_->error()." Redirecting to %s."; >+ if ( $type ne 'opac' ) { >+ warn sprintf( $warning, 'Installer' ); >+ print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure"); >+ } else { >+ warn sprintf( "OPAC: " . $warning, 'maintenance' ); >+ print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ } >+ safe_exit; >+ } >+ elsif ($_->isa('Koha::Exception::ServiceTemporarilyUnavailable')) { >+ # also, if OpacMaintenance is ON, OPAC should redirect to maintenance >+ warn "OPAC Install required, redirecting to maintenance"; >+ print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ safe_exit; >+ } >+ elsif ($_->isa('Koha::Exception::BadSystemPreference')) { >+ # If version syspref is unavailable, it means Koha is being installed, >+ # and so we must redirect to OPAC maintenance page or to the WebInstaller >+ if ( $type ne 'opac' ) { >+ warn "Install required, redirecting to Installer"; >+ print $query->redirect("/cgi-bin/koha/installer/install.pl"); >+ } else { >+ warn "OPAC Install required, redirecting to maintenance"; >+ print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ } >+ safe_exit; >+ } >+ else { >+ warn "Unknown exception class ".ref($_)."\n"; >+ die $_; #Unhandled exception case >+ } > } >- safe_exit; >- } >- >- # check that database and koha version are the same >- # there is no DB version, it's a fresh install, >- # go to web installer >- # there is a DB version, compare it to the code version >- my $kohaversion = Koha::version(); >- >- # remove the 3 last . to have a Perl number >- $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; >- $debug and print STDERR "kohaversion : $kohaversion\n"; >- if ( $version < $kohaversion ) { >- my $warning = "Database update needed, redirecting to %s. Database is $version and Koha is $kohaversion"; >- if ( $type ne 'opac' ) { >- warn sprintf( $warning, 'Installer' ); >- print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure"); >- } else { >- warn sprintf( "OPAC: " . $warning, 'maintenance' ); >- print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ else { >+ die $_; #Not a Koha::Exception-object, so rethrow it > } >- safe_exit; >- } >+ }; > } > > sub _session_log { >@@ -702,7 +718,86 @@ sub _timeout_syspref { > return $timeout; > } > >+=head checkauth >+@DEPRECATED See Bug 7174 >+ >+Compatibility layer for old Koha authentication system. >+Tries to authenticate using Koha::Auth, but if no authentication mechanism is >+identified, falls back to the deprecated legacy behaviour. >+=cut >+ > sub checkauth { >+ my @params = @_; #Clone params so we don't accidentally change them if we fallback to checkauth_legacy() >+ my $query = shift; >+ my $authnotrequired = shift; >+ my $flagsrequired = shift; >+ my $type = shift; >+ my $persona = shift; >+ >+ my $borrower; >+ try { >+ if (not($authnotrequired) && not($persona)) { >+ $borrower = Koha::Auth::authenticate($query, $flagsrequired, {authnotrequired => $authnotrequired}); >+ } >+ } catch { >+ if (blessed($_)) { >+ if ($_->isa('Koha::Exception::VersionMismatch')) { >+ >+ my $warning = $_->error()." Redirecting to %s."; >+ if ( $type ne 'opac' ) { >+ warn sprintf( $warning, 'Installer' ); >+ print $query->redirect("/cgi-bin/koha/installer/install.pl?step=1&op=updatestructure"); >+ } else { >+ warn sprintf( "OPAC: " . $warning, 'maintenance' ); >+ print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ } >+ safe_exit; >+ >+ } >+ elsif ($_->isa('Koha::Exception::BadSystemPreference')) { >+ >+ if ( $type ne 'opac' ) { >+ warn $_->error()." Redirecting to installer."; >+ print $query->redirect("/cgi-bin/koha/installer/install.pl"); >+ } else { >+ warn $_->error()." Redirecting to maintenance."; >+ print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ } >+ safe_exit; >+ >+ } >+ elsif ($_->isa('Koha::Exception::LoginFailed')) { >+ #TODO:: Return proper legacy values to get_template_and_user() when login fails. >+ } >+ elsif ($_->isa('Koha::Exception::ServiceTemporarilyUnavailable')) { >+ warn $_->error(); >+ print $query->redirect("/cgi-bin/koha/maintenance.pl"); >+ safe_exit; >+ } >+ else { >+ warn "Unknown exception class ".ref($_)."\n"; >+ die $_; #Unhandled exception case >+ } >+ } >+ else { >+ die $_; #Not a Koha::Exception-object >+ } >+ }; >+ >+ return checkauth_legacy(@params); >+} >+ >+=head checkauth_legacy >+@DEPRECATED See Bug 7174 >+ >+We are calling this because the given authentication mechanism is not yet supported >+in Koha::Auth. >+ >+See checkauth-documentation floating somewhere in this file for info about the >+legacy authentication. >+=cut >+ >+sub checkauth_legacy { > my $query = shift; > $debug and warn "Checking Auth"; > >@@ -1682,12 +1777,13 @@ sub get_session { > return $session; > } > >+#@DEPRECATED See Bug 7174 > sub checkpw { > my ( $dbh, $userid, $password, $query, $type ) = @_; > $type = 'opac' unless $type; > if ($ldap) { > $debug and print STDERR "## checkpw - checking LDAP\n"; >- my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_); # EXTERNAL AUTH >+ my ( $retval, $retcard, $retuserid ) = checkpw_ldap($userid, $password); # EXTERNAL AUTH > return 0 if $retval == -1; # Incorrect password for LDAP login attempt > ($retval) and return ( $retval, $retcard, $retuserid ); > } >@@ -1726,6 +1822,7 @@ sub checkpw { > return checkpw_internal(@_) > } > >+#@DEPRECATED See Bug 7174 > sub checkpw_internal { > my ( $dbh, $userid, $password ) = @_; > >@@ -1778,6 +1875,8 @@ sub checkpw_internal { > return 1, $cardnumber, $userid; > } > } >+ >+ #@DEPRECATED see Bug 7174. I think the demo-user should be represented with permissions instead of a hard-coded non-borrower anomaly. > if ( $userid && $userid eq 'demo' > && "$password" eq 'demo' > && C4::Context->config('demo') ) >@@ -1790,6 +1889,7 @@ sub checkpw_internal { > return 0; > } > >+#@DEPRECATED See Bug 7174 > sub checkpw_hash { > my ( $password, $stored_hash ) = @_; > >@@ -1800,6 +1900,7 @@ sub checkpw_hash { > if ( substr( $stored_hash, 0, 2 ) eq '$2' ) { > $hash = hash_password( $password, $stored_hash ); > } else { >+ #@DEPRECATED Digest::MD5, don't use it or you will get hurt. > $hash = md5_base64($password); > } > return $hash eq $stored_hash; >diff --git a/C4/Auth_with_ldap.pm b/C4/Auth_with_ldap.pm >index 58484a2..7f000c5 100644 >--- a/C4/Auth_with_ldap.pm >+++ b/C4/Auth_with_ldap.pm >@@ -103,8 +103,17 @@ sub search_method { > return $search; > } > >+=head checkpw_ldap >+ >+@RETURNS Integer, -1 if login failed >+ , 0 if connection to the LDAP server couldn't be reliably established. >+ or List of (-1|1|0, $cardnumber, $local_userid); >+ where $cardnumber is koha.borrowers.cardnumber >+ $local_userid is the koha.borrowers.userid >+=cut >+ > sub checkpw_ldap { >- my ($dbh, $userid, $password) = @_; >+ my ($userid, $password) = @_; > my @hosts = split(',', $prefhost); > my $db = Net::LDAP->new(\@hosts); > unless ( $db ) { >diff --git a/Koha/Auth.pm b/Koha/Auth.pm >new file mode 100644 >index 0000000..064e4fd >--- /dev/null >+++ b/Koha/Auth.pm >@@ -0,0 +1,187 @@ >+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 <http://www.gnu.org/licenses>. >+ >+#Define common packages >+use Modern::Perl; >+use Scalar::Util qw(blessed); >+use Try::Tiny; >+ >+#Define Koha packages >+use Koha::Auth::Route::Password; >+ >+#Define Exceptions >+use Koha::Exception::BadParameter; >+ >+#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-Signature', 'ETag', 'X-Koha-Username'); >+our @authenticationPOSTparams = ('password', 'userid', 'PT'); >+our @authenticationCookies = ('CGISESSID'); #Really we should have only one of these. >+ >+sub authenticate { >+ my ($controller, $permissions, $authParams) = @_; >+ my ($headers, $postParams, $cookies) = _authenticate_validateAndNormalizeParameters(@_); >+ >+ my $borrower; #Each authentication route returns a Koha::Borrower-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 { >+ #1. Check for password authentication, including LDAP. >+ if ($postParams->{userid} && $postParams->{password}) { >+ $borrower = Koha::Auth::Route::Password::check($headers, $postParams, $cookies, $permissions); >+ } >+ #2. Check for REST's signature-based authentication. >+ elsif ($headers->{'X-Koha-Signature'}) { >+ $borrower = Koha::Auth::Route::REST::V1::check($headers, $postParams, $cookies, $permissions); >+ } >+ #3. Check for the cookie. If cookies go stale, they block all subsequent authentication methods, so keep it down. >+ elsif ($cookies) { >+ $borrower = Koha::Auth::Route::Cookie::check($headers, $postParams, $cookies, $permissions); >+ } >+ else { #HTTP CAS ticket or shibboleth or Persona not implemented >+ ##Backwards compatibility: give the fallback signal to use the legacy authentication mechanism >+ return 'FALLBACK'; >+ } >+ } catch { >+ if (blessed($_)) { >+ if ($_->isa('Koha::Exception::LoginFailed')) { >+ if ($authParams->{authnotrequired}) { #We failed to login, but we can continue anonymously. >+ $borrower = Koha::Borrower->new(); >+ } >+ else { >+ $_->rethrow(); #Anonymous login not enabled this time >+ } >+ } >+ else { >+ $_->rethrow(); #Propagate other errors to the calling Controller to redirect as it wants. >+ } >+ } >+ else { >+ die $_; #Not a Koha::Exception-object >+ } >+ }; >+ >+ setUserEnvironment($borrower); >+} >+ >+=head _authenticate_validateAndNormalizeParameters >+ >+@THROWS Koha::Exception::BadParameter, if validating parameters fails. >+=cut >+ >+sub _authenticate_validateAndNormalizeParameters { >+ my ($controller, $permissions, $authParams) = @_; >+ >+ #Validate $controller. >+ my ($headers, $postParams, $cookies); >+ if (blessed($controller) && $controller->isa('CGI')) { >+ ($headers, $postParams, $cookies) = _normalizeCGI($controller); >+ } >+ elsif (blessed($controller) && $controller->isa('Mojolicious::Controller')) { >+ ($headers, $postParams, $cookies) = _normalizeMojolicious($controller); >+ } >+ 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 ($headers, $postParams, $cookies); >+} >+ >+=head _normalizeCGI >+Takes a CGI-object and finds the authentication markers from it. >+@PARAM1 CGI-object. >+@RETURNS List of : HASHRef of headers required for authentication, or undef >+ HASHRef of POST parameters required for authentication, or undef >+ String of the authenticaton cookie value, or undef >+=cut >+ >+sub _normalizeCGI { >+ my ($controller) = @_; >+ >+ my ($headers, $postParams, $cookies); >+ if (blessed($controller) && $controller->isa('CGI')) { >+ 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->param($authCookie)) { >+ $cookies->{$authCookie} = $val; >+ } >+ } >+ } >+ return ($headers, $postParams, $cookies); >+} >+ >+=head _normalizeMojolicious >+Takes a Mojolicious::Controller-object and finds the authentication markers from it. >+@PARAM1 Mojolicious::Controller-object. >+@RETURNS List of : HASHRef of headers required for authentication, or undef >+ HASHRef of POST parameters required for authentication, or undef >+ String of the authenticaton cookie value, or undef >+=cut >+ >+sub _normalizeMojolicious { >+ my ($controller) = @_; >+ >+ my $request = $controller->req(); >+ my ($headers, $postParams, $cookies); >+ if (blessed($controller) && $controller->isa('CGI')) { >+ my $headers = $request->headers(); >+ foreach my $authHeader (@authenticationHeaders) { >+ if (my $val = $request->headers()->$authHeader()) { >+ $headers->{$authHeader} = $val; >+ } >+ } >+ foreach my $authParam (@authenticationPOSTparams) { >+ if (my $val = $request->param($authParam)) { >+ $postParams->{$authParam} = $val; >+ } >+ } >+ foreach my $authCookie (@authenticationCookies) { >+ if (my $val = $request->cookies($authCookie)) { >+ $cookies->{$authCookie} = $val; >+ } >+ } >+ } >+ return ($headers, $postParams, $cookies); >+} >+=head setUserEnvironment >+Set the C4::Context::user_env() >+=cut >+ >+sub setUserEnvironment { >+ my ($borrower) = @_; >+} >+1; >\ No newline at end of file >diff --git a/Koha/Auth/Component.pm b/Koha/Auth/Component.pm >new file mode 100644 >index 0000000..d56f941 >--- /dev/null >+++ b/Koha/Auth/Component.pm >@@ -0,0 +1,153 @@ >+package Koha::Auth::Component; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Koha qw(version); >+use Koha::Borrowers; >+use Koha::AuthUtils; >+ >+use Koha::Exception::VersionMismatch; >+use Koha::Exception::BadSystemPreference; >+use Koha::Exception::ServiceTemporarilyUnavailable; >+ >+=head1 NAME Koha::Auth::Component >+ >+=head2 SYNOPSIS >+ >+In this package we define the authentication steps we can use to define >+authentication path behaviour. >+ >+=head2 USAGE >+ >+ use Scalar::Util qw(blessed); >+ try { >+ ... >+ Koha::Auth::Component::checkVersion(); >+ Koha::Auth::Component::checkOPACMaintenance(); >+ ... >+ } 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 >+ >+=head checkVersion >+STATIC >+ >+ Koha::Auth::Component::checkVersion(); >+ >+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 checkVersion { >+ 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'"); >+ } >+} >+ >+=head checkOPACMaintenance >+STATIC >+ >+ Koha::Auth::Component::checkOPACMaintenance(); >+ >+Checks if OPAC is under maintenance. >+ >+@THROWS Koha::Exception::ServiceTemporarilyUnavailable >+=cut >+ >+sub checkOPACMaintenance { >+ if ( C4::Context->preference('OpacMaintenance') ) { >+ Koha::Exception::ServiceTemporarilyUnavailable->throw(error => 'OPAC is under maintenance'); >+ } >+} >+ >+=head checkPassword >+STATIC >+ >+ Koha::Auth::Component::checkPassword(); >+ >+@RETURN Koha::Borrower-object if check succeedes, otherwise throws exceptions. >+@THROWS Koha::Exception::LoginFailed from Koha::AuthUtils password checks. >+=cut >+ >+sub checkPassword { >+ my ($userid, $password) = @_; >+ >+ my $borrower; >+ if (C4::Context->config('useldapserver')) { >+ $borrower = Koha::AuthUtils::checkLDAPPassword(); >+ return $borrower if $borrower; >+ } >+ elsif (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 :)"); >+ } >+ elsif (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::AuthUtils::checkKohaPassword($userid, $password); >+} >+ >+=head checkPermissions >+STATIC >+ >+ Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired); >+ >+@THROWS Koha::Exception::LoginFailed with the missing permission if permissions >+ are inadequate >+=cut >+ >+sub checkPermissions { >+ my ($borrower, $permissionsRequired) = @_; >+ >+ my ($failedPermission, $flags) = C4::Auth::haspermission($borrower, $permissionsRequired); >+ if ($failedPermission) { >+ Koha::Exception::LoginFailed->throw(error => "Missing permission '$failedPermission'."); >+ } >+} >+ >+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..1a3347d >--- /dev/null >+++ b/Koha/Auth/Route.pm >@@ -0,0 +1,65 @@ >+package Koha::Auth::Route; >+ >+# 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 <http://www.gnu.org/licenses>. >+ >+=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::<RouteName>::challenge($headers, $postParams, $cookies); >+ } >+ >+=head INPUT >+ >+Each Route gets three parameters: >+ $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' } >+ $permissions: HASHRef of Koha permissions. >+ See C4::Auth::haspermission() for example. >+ >+=head OUTPUT >+ >+Each route must return a Koha::Borrower-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::Component-subroutine calls to test for various >+authentication challenges. >+ >+=cut >+ >+1; >\ No newline at end of file >diff --git a/Koha/Auth/Route/Password.pm b/Koha/Auth/Route/Password.pm >new file mode 100644 >index 0000000..0a6d57b >--- /dev/null >+++ b/Koha/Auth/Route/Password.pm >@@ -0,0 +1,39 @@ >+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 <http://www.gnu.org/licenses>. >+ >+use Modern::Perl; >+ >+use Koha::Auth::Component; >+ >+=head challenge >+ >+@THROWS Koha::Exceptions from authentication components. >+=cut >+ >+sub challenge { >+ my ($headers, $postParams, $cookies, $permissionsRequired) = @_; >+ >+ Koha::Auth::Component::checkOPACMaintenance(); >+ Koha::Auth::Component::checkVersion(); >+ my $borrower = Koha::Auth::Component::checkPassword($postParams->{userid}, $postParams->{password}); >+ Koha::Auth::Component::checkPermissions($borrower, $permissionsRequired) if $permissionsRequired; >+ return $borrower; >+} >+ >+1; >\ No newline at end of file >diff --git a/Koha/AuthUtils.pm b/Koha/AuthUtils.pm >index b748c1b..1e48d55 100644 >--- a/Koha/AuthUtils.pm >+++ b/Koha/AuthUtils.pm >@@ -22,10 +22,15 @@ use Crypt::Eksblowfish::Bcrypt qw(bcrypt en_base64); > use Encode qw( encode is_utf8 ); > use Fcntl qw/O_RDONLY/; # O_RDONLY is used in generate_salt > >+use Koha::Borrowers; >+ >+use Koha::Exception::LoginFailed; >+ > use base 'Exporter'; > > our $VERSION = '1.01'; > our @EXPORT_OK = qw(hash_password); >+our @usernameAliasColumns = ('userid', 'cardnumber'); #Possible columns to treat as the username when authenticating. Must be UNIQUE in DB. > > =head1 NAME > >@@ -133,6 +138,152 @@ sub generate_salt { > close SOURCE; > return $string; > } >+ >+=head checkKohaPassword >+ >+ my $borrower = Koha::AuthUtils::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::Borrower, if login succeeded. >+ Sets Koha::Borrower->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 = _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::Borrowers->find({$unameAlias => $userid}); >+ if ( $borrower ) { >+ $usernameFound = 1; >+ return $borrower if ( checkHash( $password, $borrower->password ) ); >+ } >+ } >+ >+ Koha::Exception::LoginFailed->throw(error => "Password authentication failed for the given ".( ($usernameFound) ? "password" : "username and password")."."); >+} >+ >+=head checkHash >+ >+ my $passwordOk = Koha::AuthUtils::checkHash($password1, $password2) >+ >+Checks if a clear-text String/password matches the given hash when >+MD5 or Bcrypt hashing algorith is applied to it. >+ >+Bcrypt is applied if @PARAM2 starts with '$2' >+MD5 otherwise >+ >+@PARAM1 String, clear text passsword or any other String >+@PARAM2 String, hashed text password or any other String. >+@RETURN Boolean, 1 if given parameters match >+ , 0 if not >+=cut >+ >+sub checkHash { >+ my ( $password, $stored_hash ) = @_; >+ >+ $password = Encode::encode( 'UTF-8', $password ) >+ if Encode::is_utf8($password); >+ >+ return if $stored_hash eq '!'; >+ >+ my $hash; >+ if ( substr( $stored_hash, 0, 2 ) eq '$2' ) { >+ $hash = hash_password( $password, $stored_hash ); >+ } else { >+ #@DEPRECATED Digest::MD5, don't use it or you will get hurt. >+ require Digest::MD5; >+ $hash = Digest::MD5::md5_base64($password); >+ } >+ return $hash eq $stored_hash; >+} >+ >+=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::Borrower, 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::Borrower->find({userid => $local_userid}); >+ return $borrower; >+ } >+ return undef; >+} >+ >+=head _checkKohaSuperuser >+ >+ my $borrower = Koha::AuthUtils::_checkKohaSuperuser($userid, $password); >+ >+Check if the userid and password match the ones in the $KOHA_CONF >+@PARAM1 String, user identifier, either the koha.borrowers.userid, or koha.borrowers.cardnumber >+@PARAM2 String, clear text password from the authenticating user >+@RETURNS Koha::Borrower branded as superuser with ->isSuperuser() >+ or undef if user logging in is not a superuser. >+@THROWS Koha::Exception::LoginFailed if user identifier matches, but password doesn't >+=cut >+ >+sub _checkKohaSuperuser { >+ my ($userid, $password) = @_; >+ >+ if ( $userid && $userid eq C4::Context->config('user') ) { >+ if ( $password && $password eq C4::Context->config('pass') ) { >+ return _createTemporarySuperuser(); >+ } >+ else { >+ Koha::Exception::LoginFailed->throw(error => "Password authentication failed"); >+ } >+ } >+} >+ >+=head _createTemporarySuperuser >+ >+Create a temporary superuser which should be instantiated only to the environment >+and then discarded. So do not ->store() it! >+@RETURN Koha::Borrower, stamped as superuser. >+=cut >+ >+sub _createTemporarySuperuser { >+ my $borrower = Koha::Borrower->new(); >+ >+ my $superuserName = C4::Context->config('user'); >+ $borrower->isSuperuser(1); >+ $borrower->set({borrowernumber => 0, >+ userid => $superuserName, >+ cardnumber => $superuserName, >+ firstname => $superuserName, >+ surname => $superuserName, >+ branchcode => 'NO_LIBRARY_SET', >+ flags => 1, >+ email => C4::Context->preference('KohaAdminEmailAddress') >+ }); >+ return $borrower; >+} >+ > 1; > > __END__ >diff --git a/Koha/Borrower.pm b/Koha/Borrower.pm >index 93426bf..4ceae11 100644 >--- a/Koha/Borrower.pm >+++ b/Koha/Borrower.pm >@@ -43,6 +43,32 @@ sub type { > return 'Borrower'; > } > >+=head isSuperuser >+ >+ $borrower->isSuperuser(1); #Set this borrower to be a superuser >+ if ($borrower->isSuperuser()) { >+ #All your base are belong to us >+ } >+ >+Should be used from the authentication modules to mark this $borrower-object to >+have unlimited access to all Koha-features. >+This $borrower-object is the Koha DB user. >+@PARAM1 Integer, 1 means this borrower is the super/DB user. >+ "0" disables the previously set superuserness. >+=cut >+ >+sub isSuperuser { >+ my ($self, $Iam) = @_; >+ >+ if (defined $Iam && $Iam == 1) { >+ $self->{superuser} = 1; >+ } >+ elsif (defined $Iam && $Iam eq "0") { #Dealing with zero is special in Perl >+ $self->{superuser} = undef; >+ } >+ return (exists($self->{superuser}) && $self->{superuser}) ? 1 : undef; >+} >+ > =head1 AUTHOR > > Kyle M Hall <kyle@bywatersolutions.com> >diff --git a/t/db_dependent/Auth_with_ldap.t b/t/db_dependent/Auth_with_ldap.t >index 4982248..5c648f5 100755 >--- a/t/db_dependent/Auth_with_ldap.t >+++ b/t/db_dependent/Auth_with_ldap.t >@@ -74,7 +74,7 @@ subtest "checkpw_ldap tests" => sub { > > ## Connection fail tests > $desired_connection_result = 'error'; >- warning_is { $ret = C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ) } >+ warning_is { $ret = C4::Auth_with_ldap::checkpw_ldap( 'hola', password => 'hey' ) } > "LDAP connexion failed", > "checkpw_ldap prints correct warning if LDAP conexion fails"; > is( $ret, 0, "checkpw_ldap returns 0 if LDAP conexion fails"); >@@ -96,7 +96,7 @@ subtest "checkpw_ldap tests" => sub { > > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/Anonymous LDAP bind failed: LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP anonymous bind fails"; > is( $ret, 0, "checkpw_ldap returns 0 if LDAP anonymous bind fails"); >@@ -108,14 +108,14 @@ subtest "checkpw_ldap tests" => sub { > $desired_count_result = 0; # user auth problem > $non_anonymous_bind_result = 'success'; > reload_ldap_module(); >- is ( C4::Auth_with_ldap::checkpw_ldap( $dbh, 'hola', password => 'hey' ), >+ is ( C4::Auth_with_ldap::checkpw_ldap( 'hola', password => 'hey' ), > 0, "checkpw_ldap returns 0 if user lookup returns 0"); > > $non_anonymous_bind_result = 'error'; > reload_ldap_module(); > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP bind fails"; > is ( $ret, -1, "checkpw_ldap returns -1 LDAP bind fails for user (Bug 8148)"); >@@ -130,7 +130,7 @@ subtest "checkpw_ldap tests" => sub { > reload_ldap_module(); > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/LDAP bind failed as kohauser hola: LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP bind fails"; > is ( $ret, 0, "checkpw_ldap returns 0 LDAP bind fails for user (Bug 12831)"); >@@ -150,7 +150,7 @@ subtest "checkpw_ldap tests" => sub { > reload_ldap_module(); > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP bind fails"; > is ( $ret, 0, "checkpw_ldap returns 0 if bind fails"); >@@ -162,7 +162,7 @@ subtest "checkpw_ldap tests" => sub { > reload_ldap_module(); > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP bind fails"; > is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)"); >@@ -175,7 +175,7 @@ subtest "checkpw_ldap tests" => sub { > reload_ldap_module(); > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/LDAP bind failed as ldapuser cn=Manager,dc=metavore,dc=com: LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP bind fails"; > is ( $ret, 0, "checkpw_ldap returns 0 if bind fails"); >@@ -187,7 +187,7 @@ subtest "checkpw_ldap tests" => sub { > reload_ldap_module(); > > warning_like { $ret = C4::Auth_with_ldap::checkpw_ldap( >- $dbh, 'hola', password => 'hey' ) } >+ 'hola', password => 'hey' ) } > qr/LDAP Auth rejected : invalid password for user 'hola'. LDAP error #1: error_name/, > "checkpw_ldap prints correct warning if LDAP bind fails"; > is ( $ret, -1, "checkpw_ldap returns -1 if bind fails (Bug 8148)"); >diff --git a/t/db_dependent/Koha/Borrower.t b/t/db_dependent/Koha/Borrower.t >new file mode 100644 >index 0000000..75f985a >--- /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 <http://www.gnu.org/licenses>. >+ >+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::Borrower; >+ >+ >+ >+testIsSuperuser(); >+ >+ >+ >+ >+ >+################################################################################ >+#### Define test subroutines here ############################################## >+################################################################################ >+ >+=head testIsSuperuser >+@UNIT_TEST >+Tests Koha::Borrower->isSuperuser() >+=cut >+ >+sub testIsSuperuser { >+ my $borrower = Koha::Borrower->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!! >+####################### >\ No newline at end of file >-- >1.7.9.5
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Diff
|
Splinter Review
Attachments on
bug 7174
:
40672
|
40678
|
40680
|
40723
|
40739
|
41117
|
41124
|
41159
|
41256
|
41311
|
41312
|
41413
|
41536
|
41537
|
63255