From ab2fde8b7411ca10460d2beb2ef7d8dcb292eb6c Mon Sep 17 00:00:00 2001 From: Olli-Antti Kivilahti Date: Fri, 26 Jun 2015 17:17:03 +0300 Subject: [PATCH] Bug 7174 - Authentication rewriting - I have come here to chew bubblegum and kick ass... and I'm all out of bubblegum. WIP WIP, sharing just for people to keep up. Deprecating lots of stuff. Encapsulating authentication behaviour into nice reusable modules. --- C4/Auth.pm | 121 ++++++++++++++++++++++++++++++++++++----------------- Koha/AuthUtils.pm | 80 +++++++++++++++++++++++++++++++++++ 2 files changed, 163 insertions(+), 38 deletions(-) diff --git a/C4/Auth.pm b/C4/Auth.pm index 38fe02b..51f3f55 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,31 @@ 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() + #Koha::Auth::checkAuth(@params); + 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,6 +1722,7 @@ sub get_session { return $session; } +#@DEPRECATED See Bug 7174 sub checkpw { my ( $dbh, $userid, $password, $query, $type ) = @_; $type = 'opac' unless $type; @@ -1726,6 +1767,7 @@ sub checkpw { return checkpw_internal(@_) } +#@DEPRECATED See Bug 7174 sub checkpw_internal { my ( $dbh, $userid, $password ) = @_; @@ -1778,6 +1820,7 @@ sub checkpw_internal { return 1, $cardnumber, $userid; } } + if ( $userid && $userid eq 'demo' && "$password" eq 'demo' && C4::Context->config('demo') ) @@ -1790,6 +1833,7 @@ sub checkpw_internal { return 0; } +#@DEPRECATED See Bug 7174 sub checkpw_hash { my ( $password, $stored_hash ) = @_; @@ -1800,6 +1844,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/Koha/AuthUtils.pm b/Koha/AuthUtils.pm index b748c1b..dbbc6a8 100644 --- a/Koha/AuthUtils.pm +++ b/Koha/AuthUtils.pm @@ -22,6 +22,10 @@ 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'; @@ -133,6 +137,82 @@ sub generate_salt { close SOURCE; return $string; } + +=head checkKohaPassword + + my $isSuperuser = Koha::AuthUtils::checkKohaPassword($userid, $password); + +Checks of 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 String 'superuser', if user is the Koha DB user (superuser account) + undef, if the user is but a man. +@THROWS Koha::Exception::LoginFailed, if no matching password was found for all username aliases in Koha. +=cut + +sub checkKohaPassword { + my ($userid, $password) = @_; + + if ( $userid && $userid eq C4::Context->config('user') ) { + if ( $password && $password eq C4::Context->config('pass') ) { + # Koha superuser account + # C4::Context->set_userenv(0,0,C4::Context->config('user'),C4::Context->config('user'),C4::Context->config('user'),"",1); + return 'superuser'; + } + else { + Koha::Exception::LoginFailed->throw(error => "Password authentication failed"); + } + } + + 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. + my @usernameAliasColumns = ('userid', 'cardnumber'); + for my $unameAlias (@usernameAliasColumns) { + my $borrower = Koha::Borrowers->find({$unameAlias => $userid}); + if ( $borrower ) { + $usernameFound = 1; + return 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; +} + 1; __END__ -- 1.7.9.5