X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;ds=sidebyside;f=C4%2FAuth.pm;h=0d21575ae66347fa8f06316d5aef34f7109e82ee;hb=baea0a79d5d4dbe46eb052d7e52f5dcf7b5242bc;hp=d6e088aca68aafd70267a4c06d39df0d815788c5;hpb=04dd98fa846d4f28edc5811cb90e8a5ba4b6289b;p=srvgit diff --git a/C4/Auth.pm b/C4/Auth.pm index d6e088aca6..0d21575ae6 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -4,60 +4,86 @@ package C4::Auth; # # 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 2 of the License, or (at your option) any later -# version. +# 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. +# 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, write to the Free Software Foundation, Inc., -# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# You should have received a copy of the GNU General Public License +# along with Koha; if not, see . use strict; use warnings; use Digest::MD5 qw(md5_base64); -use Storable qw(thaw freeze); +use JSON qw/encode_json/; use URI::Escape; use CGI::Session; require Exporter; use C4::Context; use C4::Templates; # to get the template -use C4::Branch; # GetBranches -use C4::VirtualShelves; +use C4::Languages; +use C4::Branch; # GetBranches +use C4::Search::History; +use Koha; +use Koha::AuthUtils qw(hash_password); use POSIX qw/strftime/; use List::MoreUtils qw/ any /; +use Encode qw( encode is_utf8); # use utf8; -use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout); +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $caslogout $shib $shib_login); BEGIN { sub psgi_env { any { /^psgi\./ } keys %ENV } + sub safe_exit { - if ( psgi_env ) { die 'psgi:exit' } - else { exit } + if (psgi_env) { die 'psgi:exit' } + else { exit } } - $VERSION = 3.07.00.049; # set version for version checking + $VERSION = 3.07.00.049; # set version for version checking - $debug = $ENV{DEBUG}; - @ISA = qw(Exporter); - @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions); - @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &get_all_subpermissions &get_user_subpermissions); + $debug = $ENV{DEBUG}; + @ISA = qw(Exporter); + @EXPORT = qw(&checkauth &get_template_and_user &haspermission &get_user_subpermissions); + @EXPORT_OK = qw(&check_api_auth &get_session &check_cookie_auth &checkpw &checkpw_internal &checkpw_hash + &get_all_subpermissions &get_user_subpermissions + ); %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] ); - $ldap = C4::Context->config('useldapserver') || 0; - $cas = C4::Context->preference('casAuthentication'); - $caslogout = C4::Context->preference('casLogout'); - require C4::Auth_with_cas; # no import + $ldap = C4::Context->config('useldapserver') || 0; + $cas = C4::Context->preference('casAuthentication'); + $shib = C4::Context->config('useshibboleth') || 0; + $caslogout = C4::Context->preference('casLogout'); + require C4::Auth_with_cas; # no import + if ($ldap) { - require C4::Auth_with_ldap; - import C4::Auth_with_ldap qw(checkpw_ldap); + require C4::Auth_with_ldap; + import C4::Auth_with_ldap qw(checkpw_ldap); + } + if ($shib) { + require C4::Auth_with_shibboleth; + import C4::Auth_with_shibboleth + qw(shib_ok checkpw_shib logout_shib login_shib_url get_login_shib); + + # Check for good config + if ( shib_ok() ) { + + # Get shibboleth login attribute + $shib_login = get_login_shib(); + } + + # Bad config, disable shibboleth + else { + $shib = 0; + } } if ($cas) { - import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url); + import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url); } } @@ -68,7 +94,7 @@ C4::Auth - Authenticates Koha users =head1 SYNOPSIS - use CGI; + use CGI qw ( -utf8 ); use C4::Auth; use C4::Output; @@ -77,11 +103,11 @@ C4::Auth - Authenticates Koha users my ($template, $borrowernumber, $cookie) = get_template_and_user( { - template_name => "opac-main.tmpl", + template_name => "opac-main.tt", query => $query, type => "opac", - authnotrequired => 1, - flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' }, + authnotrequired => 0, + flagsrequired => { catalogue => '*', tools => 'import_patrons' }, } ); @@ -101,11 +127,11 @@ automatically. This gets loaded into the template. my ($template, $borrowernumber, $cookie) = get_template_and_user( { - template_name => "opac-main.tmpl", + template_name => "opac-main.tt", query => $query, type => "opac", - authnotrequired => 1, - flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' }, + authnotrequired => 0, + flagsrequired => { catalogue => '*', tools => 'import_patrons' }, } ); @@ -125,17 +151,22 @@ Output.pm module. =cut -my $SEARCH_HISTORY_INSERT_SQL =<{'template_name'}, $in->{'type'}, $in->{'query'}, $in->{'is_plugin'} ); + + my $in = shift; my ( $user, $cookie, $sessionID, $flags ); - if ( $in->{'template_name'} !~m/maintenance/ ) { + + C4::Context->interface( $in->{type} ); + + $in->{'authnotrequired'} ||= 0; + my $template = C4::Templates::gettemplate( + $in->{'template_name'}, + $in->{'type'}, + $in->{'query'}, + $in->{'is_plugin'} + ); + + if ( $in->{'template_name'} !~ m/maintenance/ ) { ( $user, $cookie, $sessionID, $flags ) = checkauth( $in->{'query'}, $in->{'authnotrequired'}, @@ -147,14 +178,16 @@ sub get_template_and_user { my $borrowernumber; if ($user) { require C4::Members; + # It's possible for $user to be the borrowernumber if they don't have a # userid defined (and are logging in through some other method, such # as SSL certs against an email address) $borrowernumber = getborrowernumber($user) if defined($user); - if (!defined($borrowernumber) && defined($user)) { - my $borrower = C4::Members::GetMember(borrowernumber => $user); + if ( !defined($borrowernumber) && defined($user) ) { + my $borrower = C4::Members::GetMember( borrowernumber => $user ); if ($borrower) { $borrowernumber = $user; + # A bit of a hack, but I don't know there's a nicer way # to do it. $user = $borrower->{firstname} . ' ' . $borrower->{surname}; @@ -162,18 +195,22 @@ sub get_template_and_user { } # user info - $template->param( loggedinusername => $user ); - $template->param( sessionID => $sessionID ); - - my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD'); - $template->param( - pubshelves => $total->{pubtotal}, - pubshelvesloop => $pubshelves, - barshelves => $total->{bartotal}, - barshelvesloop => $barshelves, - ); + $template->param( loggedinusername => $user ); + $template->param( loggedinusernumber => $borrowernumber ); + $template->param( sessionID => $sessionID ); + + if ( $in->{'type'} eq 'opac' ) { + require C4::VirtualShelves; + my ( $total, $pubshelves, $barshelves ) = C4::VirtualShelves::GetSomeShelfNames( $borrowernumber, 'MASTHEAD' ); + $template->param( + pubshelves => $total->{pubtotal}, + pubshelvesloop => $pubshelves, + barshelves => $total->{bartotal}, + barshelvesloop => $barshelves, + ); + } - my ( $borr ) = C4::Members::GetMemberDetails( $borrowernumber ); + my ($borr) = C4::Members::GetMemberDetails($borrowernumber); my @bordat; $bordat[0] = $borr; $template->param( "USER_INFO" => \@bordat ); @@ -181,18 +218,18 @@ sub get_template_and_user { my $all_perms = get_all_subpermissions(); my @flagroots = qw(circulate catalogue parameters borrowers permissions reserveforothers borrow - editcatalogue updatecharges management tools editauthorities serials reports acquisition); + editcatalogue updatecharges management tools editauthorities serials reports acquisition); + # We are going to use the $flags returned by checkauth # to create the template's parameters that will indicate # which menus the user can access. - if ( $flags && $flags->{superlibrarian}==1 ) { + if ( $flags && $flags->{superlibrarian} == 1 ) { $template->param( CAN_user_circulate => 1 ); $template->param( CAN_user_catalogue => 1 ); $template->param( CAN_user_parameters => 1 ); $template->param( CAN_user_borrowers => 1 ); $template->param( CAN_user_permissions => 1 ); $template->param( CAN_user_reserveforothers => 1 ); - $template->param( CAN_user_borrow => 1 ); $template->param( CAN_user_editcatalogue => 1 ); $template->param( CAN_user_updatecharges => 1 ); $template->param( CAN_user_acquisition => 1 ); @@ -204,21 +241,22 @@ sub get_template_and_user { $template->param( CAN_user_staffaccess => 1 ); $template->param( CAN_user_plugins => 1 ); $template->param( CAN_user_coursereserves => 1 ); - foreach my $module (keys %$all_perms) { - foreach my $subperm (keys %{ $all_perms->{$module} }) { + foreach my $module ( keys %$all_perms ) { + + foreach my $subperm ( keys %{ $all_perms->{$module} } ) { $template->param( "CAN_user_${module}_${subperm}" => 1 ); } } } - if ( $flags ) { - foreach my $module (keys %$all_perms) { - if ( $flags->{$module} == 1) { - foreach my $subperm (keys %{ $all_perms->{$module} }) { + if ($flags) { + foreach my $module ( keys %$all_perms ) { + if ( defined($flags->{$module}) && $flags->{$module} == 1 ) { + foreach my $subperm ( keys %{ $all_perms->{$module} } ) { $template->param( "CAN_user_${module}_${subperm}" => 1 ); } - } elsif ( ref($flags->{$module}) ) { - foreach my $subperm (keys %{ $flags->{$module} } ) { + } elsif ( ref( $flags->{$module} ) ) { + foreach my $subperm ( keys %{ $flags->{$module} } ) { $template->param( "CAN_user_${module}_${subperm}" => 1 ); } } @@ -226,246 +264,294 @@ sub get_template_and_user { } if ($flags) { - foreach my $module (keys %$flags) { - if ( $flags->{$module} == 1 or ref($flags->{$module}) ) { + foreach my $module ( keys %$flags ) { + if ( $flags->{$module} == 1 or ref( $flags->{$module} ) ) { $template->param( "CAN_user_$module" => 1 ); - if ($module eq "parameters") { + if ( $module eq "parameters" ) { $template->param( CAN_user_management => 1 ); } } } } + # Logged-in opac search history # If the requested template is an opac one and opac search history is enabled - if ($in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory')) { - my $dbh = C4::Context->dbh; + if ( $in->{type} eq 'opac' && C4::Context->preference('EnableOpacSearchHistory') ) { + my $dbh = C4::Context->dbh; my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?"; - my $sth = $dbh->prepare($query); + my $sth = $dbh->prepare($query); $sth->execute($borrowernumber); # If at least one search has already been performed - if ($sth->fetchrow_array > 0) { - # We show the link in opac - $template->param(ShowOpacRecentSearchLink => 1); + if ( $sth->fetchrow_array > 0 ) { + + # We show the link in opac + $template->param( EnableOpacSearchHistory => 1 ); } - # And if there's a cookie with searches performed when the user was not logged in, + # And if there are searches performed when the user was not logged in, # we add them to the logged-in search history - my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches'); - if ($searchcookie){ - $searchcookie = uri_unescape($searchcookie); - my @recentSearches = @{thaw($searchcookie) || []}; - if (@recentSearches) { - my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL); - $sth->execute( $borrowernumber, - $in->{'query'}->cookie("CGISESSID"), - $_->{'query_desc'}, - $_->{'query_cgi'}, - $_->{'total'}, - $_->{'time'}, - ) foreach @recentSearches; - - # And then, delete the cookie's content - my $newsearchcookie = $in->{'query'}->cookie( - -name => 'KohaOpacRecentSearches', - -value => freeze([]), - -HttpOnly => 1, - -expires => '' - ); - $cookie = [$cookie, $newsearchcookie]; - } + my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } ); + if (@recentSearches) { + my $dbh = C4::Context->dbh; + my $query = q{ + INSERT INTO search_history(userid, sessionid, query_desc, query_cgi, type, total, time ) + VALUES (?, ?, ?, ?, ?, ?, ?) + }; + + my $sth = $dbh->prepare($query); + $sth->execute( $borrowernumber, + $in->{query}->cookie("CGISESSID"), + $_->{query_desc}, + $_->{query_cgi}, + $_->{type} || 'biblio', + $_->{total}, + $_->{time}, + ) foreach @recentSearches; + + # clear out the search history from the session now that + # we've saved it to the database + C4::Search::History::set_to_session( { cgi => $in->{'query'}, search_history => [] } ); } + } elsif ( $in->{type} eq 'intranet' and C4::Context->preference('EnableSearchHistory') ) { + $template->param( EnableSearchHistory => 1 ); } } else { # if this is an anonymous session, setup to display public lists... - $template->param( sessionID => $sessionID ); - - my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD'); - $template->param( - pubshelves => $total->{pubtotal}, - pubshelvesloop => $pubshelves, - ); - } - # Anonymous opac search history - # If opac search history is enabled and at least one search has already been performed - if (C4::Context->preference('EnableOpacSearchHistory')) { - my $searchcookie = $in->{'query'}->cookie('KohaOpacRecentSearches'); - if ($searchcookie){ - $searchcookie = uri_unescape($searchcookie); - my @recentSearches = @{thaw($searchcookie) || []}; - # We show the link in opac - if (@recentSearches) { - $template->param(ShowOpacRecentSearchLink => 1); + # If shibboleth is enabled, and we're in an anonymous session, we should allow + # the user to attemp login via shibboleth. + if ($shib) { + $template->param( shibbolethAuthentication => $shib, + shibbolethLoginUrl => login_shib_url( $in->{'query'} ), + ); + + # If shibboleth is enabled and we have a shibboleth login attribute, + # but we are in an anonymous session, then we clearly have an invalid + # shibboleth koha account. + if ($shib_login) { + $template->param( invalidShibLogin => '1' ); } } - } - if(C4::Context->preference('dateformat')){ - $template->param(dateformat => C4::Context->preference('dateformat')) + $template->param( sessionID => $sessionID ); + + if ( $in->{'type'} eq 'opac' ){ + require C4::VirtualShelves; + my ( $total, $pubshelves ) = C4::VirtualShelves::GetSomeShelfNames( undef, 'MASTHEAD' ); + $template->param( + pubshelves => $total->{pubtotal}, + pubshelvesloop => $pubshelves, + ); + } + } + + # Anonymous opac search history + # If opac search history is enabled and at least one search has already been performed + if ( C4::Context->preference('EnableOpacSearchHistory') ) { + my @recentSearches = C4::Search::History::get_from_session( { cgi => $in->{'query'} } ); + if (@recentSearches) { + $template->param( EnableOpacSearchHistory => 1 ); + } + } + + if ( C4::Context->preference('dateformat') ) { + $template->param( dateformat => C4::Context->preference('dateformat') ); } # these template parameters are set the same regardless of $in->{'type'} + + # Set the using_https variable for templates + # FIXME Under Plack the CGI->https method always returns 'OFF' + my $https = $in->{query}->https(); + my $using_https = ( defined $https and $https ne 'OFF' ) ? 1 : 0; + $template->param( - "BiblioDefaultView".C4::Context->preference("BiblioDefaultView") => 1, - EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'), - GoogleJackets => C4::Context->preference("GoogleJackets"), - OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"), - KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"), - LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:undef), - LoginFirstname => (C4::Context->userenv?C4::Context->userenv->{"firstname"}:"Bel"), - LoginSurname => C4::Context->userenv?C4::Context->userenv->{"surname"}:"Inconnu", - emailaddress => C4::Context->userenv?C4::Context->userenv->{"emailaddress"}:undef, - loggedinpersona => C4::Context->userenv?C4::Context->userenv->{"persona"}:undef, - TagsEnabled => C4::Context->preference("TagsEnabled"), - hide_marc => C4::Context->preference("hide_marc"), - item_level_itypes => C4::Context->preference('item-level_itypes'), - patronimages => C4::Context->preference("patronimages"), - singleBranchMode => C4::Context->preference("singleBranchMode"), - XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"), - XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"), - using_https => $in->{'query'}->https() ? 1 : 0, - noItemTypeImages => C4::Context->preference("noItemTypeImages"), - marcflavour => C4::Context->preference("marcflavour"), - persona => C4::Context->preference("persona"), + "BiblioDefaultView" . C4::Context->preference("BiblioDefaultView") => 1, + EnhancedMessagingPreferences => C4::Context->preference('EnhancedMessagingPreferences'), + GoogleJackets => C4::Context->preference("GoogleJackets"), + OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"), + KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"), + LoginBranchcode => ( C4::Context->userenv ? C4::Context->userenv->{"branch"} : undef ), + LoginFirstname => ( C4::Context->userenv ? C4::Context->userenv->{"firstname"} : "Bel" ), + LoginSurname => C4::Context->userenv ? C4::Context->userenv->{"surname"} : "Inconnu", + emailaddress => C4::Context->userenv ? C4::Context->userenv->{"emailaddress"} : undef, + loggedinpersona => C4::Context->userenv ? C4::Context->userenv->{"persona"} : undef, + TagsEnabled => C4::Context->preference("TagsEnabled"), + hide_marc => C4::Context->preference("hide_marc"), + item_level_itypes => C4::Context->preference('item-level_itypes'), + patronimages => C4::Context->preference("patronimages"), + singleBranchMode => C4::Context->preference("singleBranchMode"), + XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"), + XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"), + using_https => $using_https, + noItemTypeImages => C4::Context->preference("noItemTypeImages"), + marcflavour => C4::Context->preference("marcflavour"), + persona => C4::Context->preference("persona"), + OPACBaseURL => C4::Context->preference('OPACBaseURL'), ); if ( $in->{'type'} eq "intranet" ) { $template->param( - AmazonCoverImages => C4::Context->preference("AmazonCoverImages"), - AutoLocation => C4::Context->preference("AutoLocation"), - "BiblioDefaultView".C4::Context->preference("IntranetBiblioDefaultView") => 1, - CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1, - CircAutocompl => C4::Context->preference("CircAutocompl"), - FRBRizeEditions => C4::Context->preference("FRBRizeEditions"), - IndependentBranches => C4::Context->preference("IndependentBranches"), - IntranetNav => C4::Context->preference("IntranetNav"), - IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"), - LibraryName => C4::Context->preference("LibraryName"), - LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef), - advancedMARCEditor => C4::Context->preference("advancedMARCEditor"), - canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'), - intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"), - IntranetFavicon => C4::Context->preference("IntranetFavicon"), - intranetreadinghistory => C4::Context->preference("intranetreadinghistory"), - intranetstylesheet => C4::Context->preference("intranetstylesheet"), - IntranetUserCSS => C4::Context->preference("IntranetUserCSS"), - intranetuserjs => C4::Context->preference("intranetuserjs"), - intranetbookbag => C4::Context->preference("intranetbookbag"), - suggestion => C4::Context->preference("suggestion"), - virtualshelves => C4::Context->preference("virtualshelves"), - StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"), - EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'), - LocalCoverImages => C4::Context->preference('LocalCoverImages'), - OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'), - AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'), - EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'), - UseKohaPlugins => C4::Context->preference('UseKohaPlugins'), - UseCourseReserves => C4::Context->preference("UseCourseReserves"), + AmazonCoverImages => C4::Context->preference("AmazonCoverImages"), + AutoLocation => C4::Context->preference("AutoLocation"), + "BiblioDefaultView" . C4::Context->preference("IntranetBiblioDefaultView") => 1, + CalendarFirstDayOfWeek => ( C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday" ) ? 0 : 1, + CircAutocompl => C4::Context->preference("CircAutocompl"), + FRBRizeEditions => C4::Context->preference("FRBRizeEditions"), + IndependentBranches => C4::Context->preference("IndependentBranches"), + IntranetNav => C4::Context->preference("IntranetNav"), + IntranetmainUserblock => C4::Context->preference("IntranetmainUserblock"), + LibraryName => C4::Context->preference("LibraryName"), + LoginBranchname => ( C4::Context->userenv ? C4::Context->userenv->{"branchname"} : undef ), + advancedMARCEditor => C4::Context->preference("advancedMARCEditor"), + canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'), + intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"), + IntranetFavicon => C4::Context->preference("IntranetFavicon"), + intranetreadinghistory => C4::Context->preference("intranetreadinghistory"), + intranetstylesheet => C4::Context->preference("intranetstylesheet"), + IntranetUserCSS => C4::Context->preference("IntranetUserCSS"), + IntranetUserJS => C4::Context->preference("IntranetUserJS"), + intranetbookbag => C4::Context->preference("intranetbookbag"), + suggestion => C4::Context->preference("suggestion"), + virtualshelves => C4::Context->preference("virtualshelves"), + StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"), + EasyAnalyticalRecords => C4::Context->preference('EasyAnalyticalRecords'), + LocalCoverImages => C4::Context->preference('LocalCoverImages'), + OPACLocalCoverImages => C4::Context->preference('OPACLocalCoverImages'), + AllowMultipleCovers => C4::Context->preference('AllowMultipleCovers'), + EnableBorrowerFiles => C4::Context->preference('EnableBorrowerFiles'), + UseKohaPlugins => C4::Context->preference('UseKohaPlugins'), + UseCourseReserves => C4::Context->preference("UseCourseReserves"), + useDischarge => C4::Context->preference('useDischarge'), ); } else { warn "template type should be OPAC, here it is=[" . $in->{'type'} . "]" unless ( $in->{'type'} eq 'opac' ); + #TODO : replace LibraryName syspref with 'system name', and remove this html processing my $LibraryNameTitle = C4::Context->preference("LibraryName"); $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi; $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg; - # clean up the busc param in the session if the page is not opac-detail - if (C4::Context->preference("OpacBrowseResults") && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ && $1 !~ /^(?:MARC|ISBD)?detail$/) { - my $sessionSearch = get_session($sessionID || $in->{'query'}->cookie("CGISESSID")); - $sessionSearch->clear(["busc"]) if ($sessionSearch->param("busc")); + + # clean up the busc param in the session + # if the page is not opac-detail and not the "add to list" page + # and not the "edit comments" page + if ( C4::Context->preference("OpacBrowseResults") + && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) { + my $pagename = $1; + unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/ + or $pagename =~ /^addbybiblionumber$/ + or $pagename =~ /^review$/ ) { + my $sessionSearch = get_session( $sessionID || $in->{'query'}->cookie("CGISESSID") ); + $sessionSearch->clear( ["busc"] ) if ( $sessionSearch->param("busc") ); + } } + # variables passed from CGI: opac_css_override and opac_search_limits. - my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'}; + my $opac_search_limit = $ENV{'OPAC_SEARCH_LIMIT'}; my $opac_limit_override = $ENV{'OPAC_LIMIT_OVERRIDE'}; - my $opac_name = ''; - if (($opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ && $opac_limit_override) || ($in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/)){ - $opac_name = $1; # opac_search_limit is a branch, so we use it. + my $opac_name = ''; + if ( + ( $opac_limit_override && $opac_search_limit && $opac_search_limit =~ /branch:(\w+)/ ) || + ( $in->{'query'}->param('limit') && $in->{'query'}->param('limit') =~ /branch:(\w+)/ ) || + ( $in->{'query'}->param('multibranchlimit') && $in->{'query'}->param('multibranchlimit') =~ /multibranchlimit-(\w+)/ ) + ) { + $opac_name = $1; # opac_search_limit is a branch, so we use it. } elsif ( $in->{'query'}->param('multibranchlimit') ) { $opac_name = $in->{'query'}->param('multibranchlimit'); - } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) { + } elsif ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'} ) { $opac_name = C4::Context->userenv->{'branch'}; } + $template->param( - opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"), - AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"), - AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"), - BranchesLoop => GetBranchesLoop($opac_name), - BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ), - CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1, - LibraryName => "" . C4::Context->preference("LibraryName"), - LibraryNameTitle => "" . $LibraryNameTitle, - LoginBranchname => C4::Context->userenv?C4::Context->userenv->{"branchname"}:"", - OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"), - OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"), - OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"), - OPACItemHolds => C4::Context->preference("OPACItemHolds"), - OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"), - OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"), - OPACUserCSS => "". C4::Context->preference("OPACUserCSS"), - OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"), - OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"), - OpacAuthorities => C4::Context->preference("OpacAuthorities"), - OPACBaseURL => ($in->{'query'}->https() ? "https://" : "http://") . $ENV{'SERVER_NAME'} . - ($ENV{'SERVER_PORT'} eq ($in->{'query'}->https() ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"), - opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, - opac_search_limit => $opac_search_limit, - opac_limit_override => $opac_limit_override, - OpacBrowser => C4::Context->preference("OpacBrowser"), - OpacCloud => C4::Context->preference("OpacCloud"), - OpacKohaUrl => C4::Context->preference("OpacKohaUrl"), - OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"), - OpacMainUserBlockMobile => "" . C4::Context->preference("OpacMainUserBlockMobile"), - OpacShowFiltersPulldownMobile => C4::Context->preference("OpacShowFiltersPulldownMobile"), - OpacShowLibrariesPulldownMobile => C4::Context->preference("OpacShowLibrariesPulldownMobile"), - OpacNav => "" . C4::Context->preference("OpacNav"), - OpacNavRight => "" . C4::Context->preference("OpacNavRight"), - OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"), - OpacPasswordChange => C4::Context->preference("OpacPasswordChange"), - OPACPatronDetails => C4::Context->preference("OPACPatronDetails"), - OPACPrivacy => C4::Context->preference("OPACPrivacy"), - OPACFinesTab => C4::Context->preference("OPACFinesTab"), - OpacTopissue => C4::Context->preference("OpacTopissue"), - RequestOnOpac => C4::Context->preference("RequestOnOpac"), - 'Version' => C4::Context->preference('Version'), - hidelostitems => C4::Context->preference("hidelostitems"), - mylibraryfirst => (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv) ? C4::Context->userenv->{'branch'} : '', - opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"), - opacbookbag => "" . C4::Context->preference("opacbookbag"), - opaccredits => "" . C4::Context->preference("opaccredits"), - OpacFavicon => C4::Context->preference("OpacFavicon"), - opacheader => "" . C4::Context->preference("opacheader"), - opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"), - opacreadinghistory => C4::Context->preference("opacreadinghistory"), - opacsmallimage => "" . C4::Context->preference("opacsmallimage"), - opacuserjs => C4::Context->preference("opacuserjs"), - opacuserlogin => "" . C4::Context->preference("opacuserlogin"), - ShowReviewer => C4::Context->preference("ShowReviewer"), - ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"), - suggestion => "" . C4::Context->preference("suggestion"), - virtualshelves => "" . C4::Context->preference("virtualshelves"), - OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"), - OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"), - OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"), - SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"), - SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"), - SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"), - SyndeticsTOC => C4::Context->preference("SyndeticsTOC"), - SyndeticsSummary => C4::Context->preference("SyndeticsSummary"), - SyndeticsEditions => C4::Context->preference("SyndeticsEditions"), - SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"), - SyndeticsReviews => C4::Context->preference("SyndeticsReviews"), - SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"), - SyndeticsAwards => C4::Context->preference("SyndeticsAwards"), - SyndeticsSeries => C4::Context->preference("SyndeticsSeries"), - SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"), - OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"), - PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"), + OpacAdditionalStylesheet => C4::Context->preference("OpacAdditionalStylesheet"), + AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"), + AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"), + BranchesLoop => GetBranchesLoop($opac_name), + BranchCategoriesLoop => GetBranchCategories( 'searchdomain', 1, $opac_name ), + CalendarFirstDayOfWeek => ( C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday" ) ? 0 : 1, + LibraryName => "" . C4::Context->preference("LibraryName"), + LibraryNameTitle => "" . $LibraryNameTitle, + LoginBranchname => C4::Context->userenv ? C4::Context->userenv->{"branchname"} : "", + OPACAmazonCoverImages => C4::Context->preference("OPACAmazonCoverImages"), + OPACFRBRizeEditions => C4::Context->preference("OPACFRBRizeEditions"), + OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"), + OPACShelfBrowser => "" . C4::Context->preference("OPACShelfBrowser"), + OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"), + OPACUserCSS => "" . C4::Context->preference("OPACUserCSS"), + OPACViewOthersSuggestions => "" . C4::Context->preference("OPACViewOthersSuggestions"), + OpacAuthorities => C4::Context->preference("OpacAuthorities"), + opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, + opac_search_limit => $opac_search_limit, + opac_limit_override => $opac_limit_override, + OpacBrowser => C4::Context->preference("OpacBrowser"), + OpacCloud => C4::Context->preference("OpacCloud"), + OpacKohaUrl => C4::Context->preference("OpacKohaUrl"), + OpacMainUserBlock => "" . C4::Context->preference("OpacMainUserBlock"), + OpacNav => "" . C4::Context->preference("OpacNav"), + OpacNavRight => "" . C4::Context->preference("OpacNavRight"), + OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"), + OpacPasswordChange => C4::Context->preference("OpacPasswordChange"), + OPACPatronDetails => C4::Context->preference("OPACPatronDetails"), + OPACPrivacy => C4::Context->preference("OPACPrivacy"), + OPACFinesTab => C4::Context->preference("OPACFinesTab"), + OpacTopissue => C4::Context->preference("OpacTopissue"), + RequestOnOpac => C4::Context->preference("RequestOnOpac"), + 'Version' => C4::Context->preference('Version'), + hidelostitems => C4::Context->preference("hidelostitems"), + mylibraryfirst => ( C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv ) ? C4::Context->userenv->{'branch'} : '', + opaclayoutstylesheet => "" . C4::Context->preference("opaclayoutstylesheet"), + opacbookbag => "" . C4::Context->preference("opacbookbag"), + opaccredits => "" . C4::Context->preference("opaccredits"), + OpacFavicon => C4::Context->preference("OpacFavicon"), + opacheader => "" . C4::Context->preference("opacheader"), + opaclanguagesdisplay => "" . C4::Context->preference("opaclanguagesdisplay"), + opacreadinghistory => C4::Context->preference("opacreadinghistory"), + OPACUserJS => C4::Context->preference("OPACUserJS"), + opacuserlogin => "" . C4::Context->preference("opacuserlogin"), + ShowReviewer => C4::Context->preference("ShowReviewer"), + ShowReviewerPhoto => C4::Context->preference("ShowReviewerPhoto"), + suggestion => "" . C4::Context->preference("suggestion"), + virtualshelves => "" . C4::Context->preference("virtualshelves"), + OPACSerialIssueDisplayCount => C4::Context->preference("OPACSerialIssueDisplayCount"), + OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"), + OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"), + SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"), + SyndeticsEnabled => C4::Context->preference("SyndeticsEnabled"), + SyndeticsCoverImages => C4::Context->preference("SyndeticsCoverImages"), + SyndeticsTOC => C4::Context->preference("SyndeticsTOC"), + SyndeticsSummary => C4::Context->preference("SyndeticsSummary"), + SyndeticsEditions => C4::Context->preference("SyndeticsEditions"), + SyndeticsExcerpt => C4::Context->preference("SyndeticsExcerpt"), + SyndeticsReviews => C4::Context->preference("SyndeticsReviews"), + SyndeticsAuthorNotes => C4::Context->preference("SyndeticsAuthorNotes"), + SyndeticsAwards => C4::Context->preference("SyndeticsAwards"), + SyndeticsSeries => C4::Context->preference("SyndeticsSeries"), + SyndeticsCoverImageSize => C4::Context->preference("SyndeticsCoverImageSize"), + OPACLocalCoverImages => C4::Context->preference("OPACLocalCoverImages"), + PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"), PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"), + useDischarge => C4::Context->preference('useDischarge'), ); - $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic")); + $template->param( OpacPublic => '1' ) if ( $user || C4::Context->preference("OpacPublic") ); } - return ( $template, $borrowernumber, $cookie, $flags); + + # Check if we were asked using parameters to force a specific language + if ( defined $in->{'query'}->param('language') ) { + + # Extract the language, let C4::Languages::getlanguage choose + # what to do + my $language = C4::Languages::getlanguage( $in->{'query'} ); + my $languagecookie = C4::Templates::getlanguagecookie( $in->{'query'}, $language ); + if ( ref $cookie eq 'ARRAY' ) { + push @{$cookie}, $languagecookie; + } else { + $cookie = [ $cookie, $languagecookie ]; + } + } + + return ( $template, $borrowernumber, $cookie, $flags ); } =head2 checkauth @@ -547,13 +633,14 @@ has authenticated. =cut sub _version_check { - my $type = shift; + my $type = shift; my $query = shift; my $version; + # If Version syspref is unavailable, it means Koha is beeing 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') { + 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; @@ -573,17 +660,18 @@ sub _version_check { # 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=C4::Context::KOHAVERSION; + 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){ + 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=3"); + 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'); + warn sprintf( "OPAC: " . $warning, 'maintenance' ); print $query->redirect("/cgi-bin/koha/maintenance.pl"); } safe_exit; @@ -593,22 +681,24 @@ sub _version_check { sub _session_log { (@_) or return 0; open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog"; - printf $fh join("\n",@_); + printf $fh join( "\n", @_ ); close $fh; } sub _timeout_syspref { my $timeout = C4::Context->preference('timeout') || 600; + # value in days, convert in seconds - if ($timeout =~ /(\d+)[dD]/) { + if ( $timeout =~ /(\d+)[dD]/ ) { $timeout = $1 * 86400; - }; + } return $timeout; } sub checkauth { my $query = shift; $debug and warn "Checking Auth"; + # $authnotrequired will be set for scripts which will run without authentication my $authnotrequired = shift; my $flagsrequired = shift; @@ -619,19 +709,33 @@ sub checkauth { my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); - _version_check($type,$query); + _version_check( $type, $query ); + # state variables my $loggedin = 0; my %info; my ( $userid, $cookie, $sessionID, $flags, $barshelves, $pubshelves ); my $logout = $query->param('logout.x'); + my $anon_search_history; + # This parameter is the name of the CAS server we want to authenticate against, # when using authentication against multiple CAS servers, as configured in Auth_cas_servers.yaml my $casparam = $query->param('cas'); - - if ( $userid = $ENV{'REMOTE_USER'} ) { - # Using Basic Authentication, no cookies required + my $q_userid = $query->param('userid') // ''; + + # Basic authentication is incompatible with the use of Shibboleth, + # as Shibboleth may return REMOTE_USER as a Shibboleth attribute, + # and it may not be the attribute we want to use to match the koha login. + # + # Also, do not consider an empty REMOTE_USER. + # + # Finally, after those tests, we can assume (although if it would be better with + # a syspref) that if we get a REMOTE_USER, that's from basic authentication, + # and we can affect it to $userid. + if ( !$shib and defined( $ENV{'REMOTE_USER'} ) and $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) { + + # Using Basic Authentication, no cookies required $cookie = $query->cookie( -name => 'CGISESSID', -value => '', @@ -640,72 +744,96 @@ sub checkauth { ); $loggedin = 1; } - elsif ( $persona ){ - # we dont want to set a session because we are being called by a persona callback + elsif ($persona) { + + # we dont want to set a session because we are being called by a persona callback } elsif ( $sessionID = $query->cookie("CGISESSID") ) { # assignment, not comparison my $session = get_session($sessionID); C4::Context->_new_userenv($sessionID); - my ($ip, $lasttime, $sessiontype); - if ($session){ - C4::Context::set_userenv( - $session->param('number'), $session->param('id'), + my ( $ip, $lasttime, $sessiontype ); + my $s_userid = ''; + if ($session) { + $s_userid = $session->param('id') // ''; + C4::Context->set_userenv( + $session->param('number'), $s_userid, $session->param('cardnumber'), $session->param('firstname'), $session->param('surname'), $session->param('branch'), $session->param('branchname'), $session->param('flags'), $session->param('emailaddress'), $session->param('branchprinter'), - $session->param('persona') + $session->param('persona'), $session->param('shibboleth') ); - C4::Context::set_shelves_userenv('bar',$session->param('barshelves')); - C4::Context::set_shelves_userenv('pub',$session->param('pubshelves')); - C4::Context::set_shelves_userenv('tot',$session->param('totshelves')); - $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ; - $ip = $session->param('ip'); - $lasttime = $session->param('lasttime'); - $userid = $session->param('id'); + C4::Context::set_shelves_userenv( 'bar', $session->param('barshelves') ); + C4::Context::set_shelves_userenv( 'pub', $session->param('pubshelves') ); + C4::Context::set_shelves_userenv( 'tot', $session->param('totshelves') ); + $debug and printf STDERR "AUTH_SESSION: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch); + $ip = $session->param('ip'); + $lasttime = $session->param('lasttime'); + $userid = $s_userid; $sessiontype = $session->param('sessiontype') || ''; } - if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) - || ( $cas && $query->param('ticket') ) ) { + if ( ( $query->param('koha_login_context') && ( $q_userid ne $s_userid ) ) + || ( $cas && $query->param('ticket') && !C4::Context->userenv->{'id'} ) || ( $shib && $shib_login && !$logout ) ) { + #if a user enters an id ne to the id in the current session, we need to log them in... #first we need to clear the anonymous session... - $debug and warn "query id = " . $query->param('userid') . " but session id = " . $session->param('id'); - $session->flush; + $debug and warn "query id = $q_userid but session id = $s_userid"; + $anon_search_history = $session->param('search_history'); $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $sessionID = undef; - $userid = undef; + $userid = undef; } elsif ($logout) { + # voluntary logout the user - $session->flush; + # check wether the user was using their shibboleth session or a local one + my $shibSuccess = C4::Context->userenv->{'shibboleth'}; $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); + #_session_log(sprintf "%20s from %16s logged out at %30s (manually).\n", $userid,$ip,(strftime "%c",localtime)); $sessionID = undef; $userid = undef; - if ($cas and $caslogout) { - logout_cas($query); - } + if ($cas and $caslogout) { + logout_cas($query, $type); + } + + # If we are in a shibboleth session (shibboleth is enabled, a shibboleth match attribute is set and matches koha matchpoint) + if ( $shib and $shib_login and $shibSuccess and $type eq 'opac' ) { + + # (Note: $type eq 'opac' condition should be removed when shibboleth authentication for intranet will be implemented) + logout_shib($query); + } } - elsif ( $lasttime < time() - $timeout ) { + elsif ( !$lasttime || ( $lasttime < time() - $timeout ) ) { + # timed logout $info{'timed_out'} = 1; - $session->delete() if $session; + if ($session) { + $session->delete(); + $session->flush; + } C4::Context->_unset_userenv($sessionID); + #_session_log(sprintf "%20s from %16s logged out at %30s (inactivity).\n", $userid,$ip,(strftime "%c",localtime)); $userid = undef; $sessionID = undef; } - elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) { + elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { + # Different ip than originally logged in from $info{'oldip'} = $ip; $info{'newip'} = $ENV{'REMOTE_ADDR'}; $info{'different_ip'} = 1; $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); + #_session_log(sprintf "%20s from %16s logged out at %30s (ip changed to %16s).\n", $userid,$ip,(strftime "%c",localtime), $info{'newip'}); $sessionID = undef; $userid = undef; @@ -717,8 +845,8 @@ sub checkauth { -HttpOnly => 1 ); $session->param( 'lasttime', time() ); - unless ( $sessiontype && $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in... - $flags = haspermission($userid, $flagsrequired); + unless ( $sessiontype && $sessiontype eq 'anon' ) { #if this is an anonymous session, we want to update the session, but not behave as if they are logged in... + $flags = haspermission( $userid, $flagsrequired ); if ($flags) { $loggedin = 1; } else { @@ -727,10 +855,18 @@ sub checkauth { } } } - unless ($userid || $sessionID) { + unless ( $userid || $sessionID ) { #we initiate a session prior to checking for a username to allow for anonymous sessions... my $session = get_session("") or die "Auth ERROR: Cannot get_session()"; + + # Save anonymous search history in new session so it can be retrieved + # by get_template_and_user to store it in user's search history after + # a successful login. + if ($anon_search_history) { + $session->param( 'search_history', $anon_search_history ); + } + my $sessionID = $session->id; C4::Context->_new_userenv($sessionID); $cookie = $query->cookie( @@ -738,94 +874,119 @@ sub checkauth { -value => $session->id, -HttpOnly => 1 ); - $userid = $query->param('userid'); - if ( ( $cas && $query->param('ticket') ) + $userid = $q_userid; + my $pki_field = C4::Context->preference('AllowPKIAuth'); + if ( !defined($pki_field) ) { + print STDERR "ERROR: Missing system preference AllowPKIAuth.\n"; + $pki_field = 'None'; + } + if ( ( $cas && $query->param('ticket') ) || $userid - || ( my $pki_field = C4::Context->preference('AllowPKIAuth') ) ne - 'None' || $persona ) + || ( $shib && $shib_login ) + || $pki_field ne 'None' + || $persona ) { - my $password = $query->param('password'); + my $password = $query->param('password'); + my $shibSuccess = 0; my ( $return, $cardnumber ); - if ( $cas && $query->param('ticket') ) { - my $retuserid; - ( $return, $cardnumber, $retuserid ) = - checkpw( $dbh, $userid, $password, $query ); - $userid = $retuserid; - $info{'invalidCasLogin'} = 1 unless ($return); - } - elsif ($persona) { - my $value = $persona; - - # If we're looking up the email, there's a chance that the person - # doesn't have a userid. So if there is none, we pass along the - # borrower number, and the bits of code that need to know the user - # ID will have to be smart enough to handle that. - require C4::Members; - my @users_info = C4::Members::GetBorrowersWithEmail($value); - if (@users_info) { + # If shib is enabled and we have a shib login, does the login match a valid koha user + if ( $shib && $shib_login && $type eq 'opac' ) { + my $retuserid; - # First the userid, then the borrowernum - $value = $users_info[0][1] || $users_info[0][0]; - } - else { - undef $value; - } - $return = $value ? 1 : 0; - $userid = $value; - } + # Do not pass password here, else shib will not be checked in checkpw. + ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, undef, $query ); + $userid = $retuserid; + $shibSuccess = $return; + $info{'invalidShibLogin'} = 1 unless ($return); + } - elsif ( - ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} ) - || ( $pki_field eq 'emailAddress' - && $ENV{'SSL_CLIENT_S_DN_Email'} ) - ) - { - my $value; - if ( $pki_field eq 'Common Name' ) { - $value = $ENV{'SSL_CLIENT_S_DN_CN'}; + # If shib login and match were successfull, skip further login methods + unless ($shibSuccess) { + if ( $cas && $query->param('ticket') ) { + my $retuserid; + ( $return, $cardnumber, $retuserid ) = + checkpw( $dbh, $userid, $password, $query, $type ); + $userid = $retuserid; + $info{'invalidCasLogin'} = 1 unless ($return); } - elsif ( $pki_field eq 'emailAddress' ) { - $value = $ENV{'SSL_CLIENT_S_DN_Email'}; - # If we're looking up the email, there's a chance that the person - # doesn't have a userid. So if there is none, we pass along the - # borrower number, and the bits of code that need to know the user - # ID will have to be smart enough to handle that. + elsif ($persona) { + my $value = $persona; + + # If we're looking up the email, there's a chance that the person + # doesn't have a userid. So if there is none, we pass along the + # borrower number, and the bits of code that need to know the user + # ID will have to be smart enough to handle that. require C4::Members; my @users_info = C4::Members::GetBorrowersWithEmail($value); if (@users_info) { # First the userid, then the borrowernum $value = $users_info[0][1] || $users_info[0][0]; - } else { + } + else { undef $value; } + $return = $value ? 1 : 0; + $userid = $value; } + elsif ( + ( $pki_field eq 'Common Name' && $ENV{'SSL_CLIENT_S_DN_CN'} ) + || ( $pki_field eq 'emailAddress' + && $ENV{'SSL_CLIENT_S_DN_Email'} ) + ) + { + my $value; + if ( $pki_field eq 'Common Name' ) { + $value = $ENV{'SSL_CLIENT_S_DN_CN'}; + } + elsif ( $pki_field eq 'emailAddress' ) { + $value = $ENV{'SSL_CLIENT_S_DN_Email'}; + + # If we're looking up the email, there's a chance that the person + # doesn't have a userid. So if there is none, we pass along the + # borrower number, and the bits of code that need to know the user + # ID will have to be smart enough to handle that. + require C4::Members; + my @users_info = C4::Members::GetBorrowersWithEmail($value); + if (@users_info) { + + # First the userid, then the borrowernum + $value = $users_info[0][1] || $users_info[0][0]; + } else { + undef $value; + } + } - $return = $value ? 1 : 0; - $userid = $value; + $return = $value ? 1 : 0; + $userid = $value; - } - else { - my $retuserid; - ( $return, $cardnumber, $retuserid ) = - checkpw( $dbh, $userid, $password, $query ); - $userid = $retuserid if ( $retuserid ne '' ); - } - if ($return) { - #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime)); - if ( $flags = haspermission( $userid, $flagsrequired ) ) { + } + else { + my $retuserid; + ( $return, $cardnumber, $retuserid ) = + checkpw( $dbh, $userid, $password, $query, $type ); + $userid = $retuserid if ($retuserid); + $info{'invalid_username_or_password'} = 1 unless ($return); + } + } + + # $return: 1 = valid user, 2 = superlibrarian + if ($return) { + + #_session_log(sprintf "%20s from %16s logged in at %30s.\n", $userid,$ENV{'REMOTE_ADDR'},(strftime '%c', localtime)); + if ( $flags = haspermission( $userid, $flagsrequired ) ) { $loggedin = 1; } - else { + else { $info{'nopermission'} = 1; C4::Context->_unset_userenv($sessionID); } - my ($borrowernumber, $firstname, $surname, $userflags, - $branchcode, $branchname, $branchprinter, $emailaddress); + my ( $borrowernumber, $firstname, $surname, $userflags, + $branchcode, $branchname, $branchprinter, $emailaddress ); if ( $return == 1 ) { my $select = " @@ -838,49 +999,52 @@ sub checkauth { "; my $sth = $dbh->prepare("$select where userid=?"); $sth->execute($userid); - unless ($sth->rows) { + unless ( $sth->rows ) { $debug and print STDERR "AUTH_1: no rows for userid='$userid'\n"; $sth = $dbh->prepare("$select where cardnumber=?"); $sth->execute($cardnumber); - unless ($sth->rows) { + unless ( $sth->rows ) { $debug and print STDERR "AUTH_2a: no rows for cardnumber='$cardnumber'\n"; $sth->execute($userid); - unless ($sth->rows) { + unless ( $sth->rows ) { $debug and print STDERR "AUTH_2b: no rows for userid='$userid' AS cardnumber\n"; } } } - if ($sth->rows) { - ($borrowernumber, $firstname, $surname, $userflags, - $branchcode, $branchname, $branchprinter, $emailaddress) = $sth->fetchrow; + if ( $sth->rows ) { + ( $borrowernumber, $firstname, $surname, $userflags, + $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow; $debug and print STDERR "AUTH_3 results: " . - "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n"; + "$cardnumber,$borrowernumber,$userid,$firstname,$surname,$userflags,$branchcode,$emailaddress\n"; } else { print STDERR "AUTH_3: no results for userid='$userid', cardnumber='$cardnumber'.\n"; } -# launch a sequence to check if we have a ip for the branch, i -# if we have one we replace the branchcode of the userenv by the branch bound in the ip. + # launch a sequence to check if we have a ip for the branch, i + # if we have one we replace the branchcode of the userenv by the branch bound in the ip. + + my $ip = $ENV{'REMOTE_ADDR'}; - my $ip = $ENV{'REMOTE_ADDR'}; # if they specify at login, use that - if ($query->param('branch')) { - $branchcode = $query->param('branch'); + if ( $query->param('branch') ) { + $branchcode = $query->param('branch'); $branchname = GetBranchName($branchcode); } my $branches = GetBranches(); - if (C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation')){ + if ( C4::Context->boolean_preference('IndependentBranches') && C4::Context->boolean_preference('Autolocation') ) { + # we have to check they are coming from the right ip range my $domain = $branches->{$branchcode}->{'branchip'}; - if ($ip !~ /^$domain/){ - $loggedin=0; + if ( $ip !~ /^$domain/ ) { + $loggedin = 0; $info{'wrongip'} = 1; } } my @branchesloop; foreach my $br ( keys %$branches ) { + # now we work with the treatment of ip my $domain = $branches->{$br}->{'branchip'}; if ( $domain && $ip =~ /^$domain/ ) { @@ -891,63 +1055,72 @@ sub checkauth { $branchname = $branches->{$br}->{'branchname'}; } } - $session->param('number',$borrowernumber); - $session->param('id',$userid); - $session->param('cardnumber',$cardnumber); - $session->param('firstname',$firstname); - $session->param('surname',$surname); - $session->param('branch',$branchcode); - $session->param('branchname',$branchname); - $session->param('flags',$userflags); - $session->param('emailaddress',$emailaddress); - $session->param('ip',$session->remote_addr()); - $session->param('lasttime',time()); - $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map {$session->param($_)} qw(cardnumber firstname surname branch) ; + $session->param( 'number', $borrowernumber ); + $session->param( 'id', $userid ); + $session->param( 'cardnumber', $cardnumber ); + $session->param( 'firstname', $firstname ); + $session->param( 'surname', $surname ); + $session->param( 'branch', $branchcode ); + $session->param( 'branchname', $branchname ); + $session->param( 'flags', $userflags ); + $session->param( 'emailaddress', $emailaddress ); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'lasttime', time() ); + $session->param( 'shibboleth', $shibSuccess ); + $debug and printf STDERR "AUTH_4: (%s)\t%s %s - %s\n", map { $session->param($_) } qw(cardnumber firstname surname branch); } elsif ( $return == 2 ) { + #We suppose the user is the superlibrarian $borrowernumber = 0; - $session->param('number',0); - $session->param('id',C4::Context->config('user')); - $session->param('cardnumber',C4::Context->config('user')); - $session->param('firstname',C4::Context->config('user')); - $session->param('surname',C4::Context->config('user')); - $session->param('branch','NO_LIBRARY_SET'); - $session->param('branchname','NO_LIBRARY_SET'); - $session->param('flags',1); - $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress')); - $session->param('ip',$session->remote_addr()); - $session->param('lasttime',time()); + $session->param( 'number', 0 ); + $session->param( 'id', C4::Context->config('user') ); + $session->param( 'cardnumber', C4::Context->config('user') ); + $session->param( 'firstname', C4::Context->config('user') ); + $session->param( 'surname', C4::Context->config('user') ); + $session->param( 'branch', 'NO_LIBRARY_SET' ); + $session->param( 'branchname', 'NO_LIBRARY_SET' ); + $session->param( 'flags', 1 ); + $session->param( 'emailaddress', C4::Context->preference('KohaAdminEmailAddress') ); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'lasttime', time() ); } - if ($persona){ - $session->param('persona',1); + if ($persona) { + $session->param( 'persona', 1 ); } - C4::Context::set_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'), $session->param('flags'), $session->param('emailaddress'), $session->param('branchprinter'), - $session->param('persona') + $session->param('persona'), $session->param('shibboleth') ); } + # $return: 0 = invalid user + # reset to anonymous session else { + $debug and warn "Login failed, resetting anonymous session..."; if ($userid) { $info{'invalid_username_or_password'} = 1; C4::Context->_unset_userenv($sessionID); } + $session->param( 'lasttime', time() ); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'sessiontype', 'anon' ); } } # END if ( $userid = $query->param('userid') ) - elsif ($type eq "opac") { + elsif ( $type eq "opac" ) { + # if we are here this is an anonymous session; add public lists to it and a few other items... # anonymous sessions are created only for the OPAC $debug and warn "Initiating an anonymous session..."; # setting a couple of other session vars... - $session->param('ip',$session->remote_addr()); - $session->param('lasttime',time()); - $session->param('sessiontype','anon'); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'lasttime', time() ); + $session->param( 'sessiontype', 'anon' ); } } # END unless ($userid) @@ -965,11 +1138,11 @@ sub checkauth { return ( $userid, $cookie, $sessionID, $flags ); } -# -# -# AUTH rejected, show the login/password template, after checking the DB. -# -# + # + # + # AUTH rejected, show the login/password template, after checking the DB. + # + # # get the inputs from the incoming query my @inputs = (); @@ -983,57 +1156,59 @@ sub checkauth { $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi; $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg; - my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl'; - my $template = C4::Templates::gettemplate($template_name, $type, $query ); + my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tt' : 'auth.tt'; + my $template = C4::Templates::gettemplate( $template_name, $type, $query ); $template->param( - branchloop => GetBranchesLoop(), - opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"), - opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"), - login => 1, - INPUTS => \@inputs, - casAuthentication => C4::Context->preference("casAuthentication"), - suggestion => C4::Context->preference("suggestion"), - virtualshelves => C4::Context->preference("virtualshelves"), - LibraryName => "" . C4::Context->preference("LibraryName"), - LibraryNameTitle => "" . $LibraryNameTitle, - opacuserlogin => C4::Context->preference("opacuserlogin"), - OpacNav => C4::Context->preference("OpacNav"), - OpacNavRight => C4::Context->preference("OpacNavRight"), - OpacNavBottom => C4::Context->preference("OpacNavBottom"), - opaccredits => C4::Context->preference("opaccredits"), - OpacFavicon => C4::Context->preference("OpacFavicon"), - opacreadinghistory => C4::Context->preference("opacreadinghistory"), - opacsmallimage => C4::Context->preference("opacsmallimage"), - opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"), - opacuserjs => C4::Context->preference("opacuserjs"), - opacbookbag => "" . C4::Context->preference("opacbookbag"), - OpacCloud => C4::Context->preference("OpacCloud"), - OpacTopissue => C4::Context->preference("OpacTopissue"), - OpacAuthorities => C4::Context->preference("OpacAuthorities"), - OpacBrowser => C4::Context->preference("OpacBrowser"), - opacheader => C4::Context->preference("opacheader"), - TagsEnabled => C4::Context->preference("TagsEnabled"), - OPACUserCSS => C4::Context->preference("OPACUserCSS"), - intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"), - intranetstylesheet => C4::Context->preference("intranetstylesheet"), - intranetbookbag => C4::Context->preference("intranetbookbag"), - IntranetNav => C4::Context->preference("IntranetNav"), - IntranetFavicon => C4::Context->preference("IntranetFavicon"), - intranetuserjs => C4::Context->preference("intranetuserjs"), - IndependentBranches=> C4::Context->preference("IndependentBranches"), - AutoLocation => C4::Context->preference("AutoLocation"), - wrongip => $info{'wrongip'}, - PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"), + branchloop => GetBranchesLoop(), + OpacAdditionalStylesheet => C4::Context->preference("OpacAdditionalStylesheet"), + opaclayoutstylesheet => C4::Context->preference("opaclayoutstylesheet"), + login => 1, + INPUTS => \@inputs, + casAuthentication => C4::Context->preference("casAuthentication"), + shibbolethAuthentication => $shib, + SessionRestrictionByIP => C4::Context->preference("SessionRestrictionByIP"), + suggestion => C4::Context->preference("suggestion"), + virtualshelves => C4::Context->preference("virtualshelves"), + LibraryName => "" . C4::Context->preference("LibraryName"), + LibraryNameTitle => "" . $LibraryNameTitle, + opacuserlogin => C4::Context->preference("opacuserlogin"), + OpacNav => C4::Context->preference("OpacNav"), + OpacNavRight => C4::Context->preference("OpacNavRight"), + OpacNavBottom => C4::Context->preference("OpacNavBottom"), + opaccredits => C4::Context->preference("opaccredits"), + OpacFavicon => C4::Context->preference("OpacFavicon"), + opacreadinghistory => C4::Context->preference("opacreadinghistory"), + opaclanguagesdisplay => C4::Context->preference("opaclanguagesdisplay"), + OPACUserJS => C4::Context->preference("OPACUserJS"), + opacbookbag => "" . C4::Context->preference("opacbookbag"), + OpacCloud => C4::Context->preference("OpacCloud"), + OpacTopissue => C4::Context->preference("OpacTopissue"), + OpacAuthorities => C4::Context->preference("OpacAuthorities"), + OpacBrowser => C4::Context->preference("OpacBrowser"), + opacheader => C4::Context->preference("opacheader"), + TagsEnabled => C4::Context->preference("TagsEnabled"), + OPACUserCSS => C4::Context->preference("OPACUserCSS"), + intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"), + intranetstylesheet => C4::Context->preference("intranetstylesheet"), + intranetbookbag => C4::Context->preference("intranetbookbag"), + IntranetNav => C4::Context->preference("IntranetNav"), + IntranetFavicon => C4::Context->preference("IntranetFavicon"), + IntranetUserJS => C4::Context->preference("IntranetUserJS"), + IndependentBranches => C4::Context->preference("IndependentBranches"), + AutoLocation => C4::Context->preference("AutoLocation"), + wrongip => $info{'wrongip'}, + PatronSelfRegistration => C4::Context->preference("PatronSelfRegistration"), PatronSelfRegistrationDefaultCategory => C4::Context->preference("PatronSelfRegistrationDefaultCategory"), - persona => C4::Context->preference("Persona"), - opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, + persona => C4::Context->preference("Persona"), + opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, ); - $template->param( OpacPublic => C4::Context->preference("OpacPublic")); + $template->param( OpacPublic => C4::Context->preference("OpacPublic") ); $template->param( loginprompt => 1 ) unless $info{'nopermission'}; - if($type eq 'opac'){ - my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD'); + if ( $type eq 'opac' ) { + require C4::VirtualShelves; + my ( $total, $pubshelves ) = C4::VirtualShelves::GetSomeShelfNames( undef, 'MASTHEAD' ); $template->param( pubshelves => $total->{pubtotal}, pubshelvesloop => $pubshelves, @@ -1042,24 +1217,31 @@ sub checkauth { if ($cas) { - # Is authentication against multiple CAS servers enabled? - if (C4::Auth_with_cas::multipleAuth && !$casparam) { - my $casservers = C4::Auth_with_cas::getMultipleAuth(); - my @tmplservers; - foreach my $key (keys %$casservers) { - push @tmplservers, {name => $key, value => login_cas_url($query, $key) . "?cas=$key" }; + # Is authentication against multiple CAS servers enabled? + if ( C4::Auth_with_cas::multipleAuth && !$casparam ) { + my $casservers = C4::Auth_with_cas::getMultipleAuth(); + my @tmplservers; + foreach my $key ( keys %$casservers ) { + push @tmplservers, { name => $key, value => login_cas_url( $query, $key, $type ) . "?cas=$key" }; + } + $template->param( + casServersLoop => \@tmplservers + ); + } else { + $template->param( + casServerUrl => login_cas_url($query, undef, $type), + ); } + $template->param( - casServersLoop => \@tmplservers - ); - } else { - $template->param( - casServerUrl => login_cas_url($query), + invalidCasLogin => $info{'invalidCasLogin'} ); } - $template->param( - invalidCasLogin => $info{'invalidCasLogin'} + if ($shib) { + $template->param( + shibbolethAuthentication => $shib, + shibbolethLoginUrl => login_shib_url($query), ); } @@ -1068,13 +1250,14 @@ sub checkauth { url => $self_url, LibraryName => C4::Context->preference("LibraryName"), ); - $template->param( %info ); -# $cookie = $query->cookie(CGISESSID => $session->id -# ); + $template->param(%info); + + # $cookie = $query->cookie(CGISESSID => $session->id + # ); print $query->header( - -type => 'text/html', + -type => 'text/html', -charset => 'utf-8', - -cookie => $cookie + -cookie => $cookie ), $template->output; safe_exit; @@ -1115,23 +1298,25 @@ Possible return values in C<$status> are: =cut sub check_api_auth { - my $query = shift; + my $query = shift; my $flagsrequired = shift; my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); - unless (C4::Context->preference('Version')) { + unless ( C4::Context->preference('Version') ) { + # database has not been installed yet - return ("maintenance", undef, undef); + return ( "maintenance", undef, undef ); } - my $kohaversion=C4::Context::KOHAVERSION; + my $kohaversion = Koha::version(); $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - if (C4::Context->preference('Version') < $kohaversion) { + if ( C4::Context->preference('Version') < $kohaversion ) { + # database in need of version update; assume that # no API should be called while databsae is in # this condition. - return ("maintenance", undef, undef); + return ( "maintenance", undef, undef ); } # FIXME -- most of what follows is a copy-and-paste @@ -1146,14 +1331,14 @@ sub check_api_auth { # a form submission, assume that any current cookie # is to be ignored my $sessionID = undef; - unless ($query->param('userid')) { + unless ( $query->param('userid') ) { $sessionID = $query->cookie("CGISESSID"); } - if ($sessionID && not ($cas && $query->param('PT')) ) { + if ( $sessionID && not( $cas && $query->param('PT') ) ) { my $session = get_session($sessionID); C4::Context->_new_userenv($sessionID); if ($session) { - C4::Context::set_userenv( + C4::Context->set_userenv( $session->param('number'), $session->param('id'), $session->param('cardnumber'), $session->param('firstname'), $session->param('surname'), $session->param('branch'), @@ -1161,75 +1346,84 @@ sub check_api_auth { $session->param('emailaddress'), $session->param('branchprinter') ); - my $ip = $session->param('ip'); + my $ip = $session->param('ip'); my $lasttime = $session->param('lasttime'); - my $userid = $session->param('id'); + my $userid = $session->param('id'); if ( $lasttime < time() - $timeout ) { + # time out $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; - return ("expired", undef, undef); - } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) { + return ( "expired", undef, undef ); + } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { + # IP address changed $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; - return ("expired", undef, undef); + return ( "expired", undef, undef ); } else { my $cookie = $query->cookie( - -name => 'CGISESSID', - -value => $session->id, + -name => 'CGISESSID', + -value => $session->id, -HttpOnly => 1, ); - $session->param('lasttime',time()); - my $flags = haspermission($userid, $flagsrequired); + $session->param( 'lasttime', time() ); + my $flags = haspermission( $userid, $flagsrequired ); if ($flags) { - return ("ok", $cookie, $sessionID); + return ( "ok", $cookie, $sessionID ); } else { $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; - return ("failed", undef, undef); + return ( "failed", undef, undef ); } } } else { - return ("expired", undef, undef); + return ( "expired", undef, undef ); } } else { + # new login - my $userid = $query->param('userid'); + my $userid = $query->param('userid'); my $password = $query->param('password'); - my ($return, $cardnumber); + my ( $return, $cardnumber ); - # Proxy CAS auth - if ($cas && $query->param('PT')) { - my $retuserid; - $debug and print STDERR "## check_api_auth - checking CAS\n"; - # In case of a CAS authentication, we use the ticket instead of the password - my $PT = $query->param('PT'); - ($return,$cardnumber,$userid) = check_api_auth_cas($dbh, $PT, $query); # EXTERNAL AUTH - } else { - # User / password auth - unless ($userid and $password) { - # caller did something wrong, fail the authenticateion - return ("failed", undef, undef); + # Proxy CAS auth + if ( $cas && $query->param('PT') ) { + my $retuserid; + $debug and print STDERR "## check_api_auth - checking CAS\n"; + + # In case of a CAS authentication, we use the ticket instead of the password + my $PT = $query->param('PT'); + ( $return, $cardnumber, $userid ) = check_api_auth_cas( $dbh, $PT, $query ); # EXTERNAL AUTH + } else { + + # User / password auth + unless ( $userid and $password ) { + + # caller did something wrong, fail the authenticateion + return ( "failed", undef, undef ); + } + ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query ); } - ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query ); - } - if ($return and haspermission( $userid, $flagsrequired)) { + if ( $return and haspermission( $userid, $flagsrequired ) ) { my $session = get_session(""); - return ("failed", undef, undef) unless $session; + return ( "failed", undef, undef ) unless $session; my $sessionID = $session->id; C4::Context->_new_userenv($sessionID); my $cookie = $query->cookie( - -name => 'CGISESSID', - -value => $sessionID, + -name => 'CGISESSID', + -value => $sessionID, -HttpOnly => 1, ); if ( $return == 1 ) { @@ -1249,10 +1443,10 @@ sub check_api_auth { $branchprinter, $emailaddress ) = $sth->fetchrow if ( $sth->rows ); - unless ($sth->rows ) { + unless ( $sth->rows ) { my $sth = $dbh->prepare( "select borrowernumber, firstname, surname, flags, borrowers.branchcode, branches.branchname as branchname, branches.branchprinter as branchprinter, email from borrowers left join branches on borrowers.branchcode=branches.branchcode where cardnumber=?" - ); + ); $sth->execute($cardnumber); ( $borrowernumber, $firstname, $surname, @@ -1263,21 +1457,23 @@ sub check_api_auth { unless ( $sth->rows ) { $sth->execute($userid); ( - $borrowernumber, $firstname, $surname, $userflags, - $branchcode, $branchname, $branchprinter, $emailaddress + $borrowernumber, $firstname, $surname, $userflags, + $branchcode, $branchname, $branchprinter, $emailaddress ) = $sth->fetchrow if ( $sth->rows ); } } - my $ip = $ENV{'REMOTE_ADDR'}; + my $ip = $ENV{'REMOTE_ADDR'}; + # if they specify at login, use that - if ($query->param('branch')) { - $branchcode = $query->param('branch'); + if ( $query->param('branch') ) { + $branchcode = $query->param('branch'); $branchname = GetBranchName($branchcode); } my $branches = GetBranches(); my @branchesloop; foreach my $br ( keys %$branches ) { + # now we work with the treatment of ip my $domain = $branches->{$br}->{'branchip'}; if ( $domain && $ip =~ /^$domain/ ) { @@ -1288,41 +1484,42 @@ sub check_api_auth { $branchname = $branches->{$br}->{'branchname'}; } } - $session->param('number',$borrowernumber); - $session->param('id',$userid); - $session->param('cardnumber',$cardnumber); - $session->param('firstname',$firstname); - $session->param('surname',$surname); - $session->param('branch',$branchcode); - $session->param('branchname',$branchname); - $session->param('flags',$userflags); - $session->param('emailaddress',$emailaddress); - $session->param('ip',$session->remote_addr()); - $session->param('lasttime',time()); + $session->param( 'number', $borrowernumber ); + $session->param( 'id', $userid ); + $session->param( 'cardnumber', $cardnumber ); + $session->param( 'firstname', $firstname ); + $session->param( 'surname', $surname ); + $session->param( 'branch', $branchcode ); + $session->param( 'branchname', $branchname ); + $session->param( 'flags', $userflags ); + $session->param( 'emailaddress', $emailaddress ); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'lasttime', time() ); } elsif ( $return == 2 ) { + #We suppose the user is the superlibrarian - $session->param('number',0); - $session->param('id',C4::Context->config('user')); - $session->param('cardnumber',C4::Context->config('user')); - $session->param('firstname',C4::Context->config('user')); - $session->param('surname',C4::Context->config('user')); - $session->param('branch','NO_LIBRARY_SET'); - $session->param('branchname','NO_LIBRARY_SET'); - $session->param('flags',1); - $session->param('emailaddress', C4::Context->preference('KohaAdminEmailAddress')); - $session->param('ip',$session->remote_addr()); - $session->param('lasttime',time()); + $session->param( 'number', 0 ); + $session->param( 'id', C4::Context->config('user') ); + $session->param( 'cardnumber', C4::Context->config('user') ); + $session->param( 'firstname', C4::Context->config('user') ); + $session->param( 'surname', C4::Context->config('user') ); + $session->param( 'branch', 'NO_LIBRARY_SET' ); + $session->param( 'branchname', 'NO_LIBRARY_SET' ); + $session->param( 'flags', 1 ); + $session->param( 'emailaddress', C4::Context->preference('KohaAdminEmailAddress') ); + $session->param( 'ip', $session->remote_addr() ); + $session->param( 'lasttime', time() ); } - C4::Context::set_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'), $session->param('flags'), $session->param('emailaddress'), $session->param('branchprinter') ); - return ("ok", $cookie, $sessionID); + return ( "ok", $cookie, $sessionID ); } else { - return ("failed", undef, undef); + return ( "failed", undef, undef ); } } } @@ -1355,23 +1552,25 @@ Possible return values in C<$status> are: =cut sub check_cookie_auth { - my $cookie = shift; + my $cookie = shift; my $flagsrequired = shift; my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); - unless (C4::Context->preference('Version')) { + unless ( C4::Context->preference('Version') ) { + # database has not been installed yet - return ("maintenance", undef); + return ( "maintenance", undef ); } - my $kohaversion=C4::Context::KOHAVERSION; + my $kohaversion = Koha::version(); $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; - if (C4::Context->preference('Version') < $kohaversion) { + if ( C4::Context->preference('Version') < $kohaversion ) { + # database in need of version update; assume that # no API should be called while databsae is in # this condition. - return ("maintenance", undef); + return ( "maintenance", undef ); } # FIXME -- most of what follows is a copy-and-paste @@ -1385,14 +1584,14 @@ sub check_cookie_auth { # however, if a userid parameter is present (i.e., from # a form submission, assume that any current cookie # is to be ignored - unless (defined $cookie and $cookie) { - return ("failed", undef); + unless ( defined $cookie and $cookie ) { + return ( "failed", undef ); } my $sessionID = $cookie; - my $session = get_session($sessionID); + my $session = get_session($sessionID); C4::Context->_new_userenv($sessionID); if ($session) { - C4::Context::set_userenv( + C4::Context->set_userenv( $session->param('number'), $session->param('id'), $session->param('cardnumber'), $session->param('firstname'), $session->param('surname'), $session->param('branch'), @@ -1400,38 +1599,43 @@ sub check_cookie_auth { $session->param('emailaddress'), $session->param('branchprinter') ); - my $ip = $session->param('ip'); + my $ip = $session->param('ip'); my $lasttime = $session->param('lasttime'); - my $userid = $session->param('id'); + my $userid = $session->param('id'); if ( $lasttime < time() - $timeout ) { + # time out $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; return ("expired", undef); - } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) { + } elsif ( C4::Context->preference('SessionRestrictionByIP') && $ip ne $ENV{'REMOTE_ADDR'} ) { + # IP address changed $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; - return ("expired", undef); + return ( "expired", undef ); } else { - $session->param('lasttime',time()); - my $flags = haspermission($userid, $flagsrequired); + $session->param( 'lasttime', time() ); + my $flags = haspermission( $userid, $flagsrequired ); if ($flags) { - return ("ok", $sessionID); + return ( "ok", $sessionID ); } else { $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; - return ("failed", undef); + return ( "failed", undef ); } } } else { - return ("expired", undef); + return ( "expired", undef ); } } @@ -1451,97 +1655,149 @@ will be created. =cut sub get_session { - my $sessionID = shift; + my $sessionID = shift; my $storage_method = C4::Context->preference('SessionStorage'); - my $dbh = C4::Context->dbh; + my $dbh = C4::Context->dbh; my $session; - if ($storage_method eq 'mysql'){ - $session = new CGI::Session("driver:MySQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh}); + if ( $storage_method eq 'mysql' ) { + $session = new CGI::Session( "driver:MySQL;serializer:yaml;id:md5", $sessionID, { Handle => $dbh } ); } - elsif ($storage_method eq 'Pg') { - $session = new CGI::Session("driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, {Handle=>$dbh}); + elsif ( $storage_method eq 'Pg' ) { + $session = new CGI::Session( "driver:PostgreSQL;serializer:yaml;id:md5", $sessionID, { Handle => $dbh } ); } - elsif ($storage_method eq 'memcached' && C4::Context->ismemcached){ - $session = new CGI::Session("driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } ); + elsif ( $storage_method eq 'memcached' && C4::Context->ismemcached ) { + $session = new CGI::Session( "driver:memcached;serializer:yaml;id:md5", $sessionID, { Memcached => C4::Context->memcached } ); } else { # catch all defaults to tmp should work on all systems - $session = new CGI::Session("driver:File;serializer:yaml;id:md5", $sessionID, {Directory=>'/tmp'}); + $session = new CGI::Session( "driver:File;serializer:yaml;id:md5", $sessionID, { Directory => '/tmp' } ); } return $session; } sub checkpw { - - my ( $dbh, $userid, $password, $query ) = @_; + 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 - ($retval) and return ($retval,$retcard,$retuserid); + my ( $retval, $retcard, $retuserid ) = checkpw_ldap(@_); # EXTERNAL AUTH + return 0 if $retval == -1; # Incorrect password for LDAP login attempt + ($retval) and return ( $retval, $retcard, $retuserid ); } - if ($cas && $query && $query->param('ticket')) { + if ( $cas && $query && $query->param('ticket') ) { $debug and print STDERR "## checkpw - checking CAS\n"; - # In case of a CAS authentication, we use the ticket instead of the password - my $ticket = $query->param('ticket'); - my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # EXTERNAL AUTH - ($retval) and return ($retval,$retcard,$retuserid); - return 0; + + # In case of a CAS authentication, we use the ticket instead of the password + my $ticket = $query->param('ticket'); + $query->delete('ticket'); # remove ticket to come back to original URL + my ( $retval, $retcard, $retuserid ) = checkpw_cas( $dbh, $ticket, $query, $type ); # EXTERNAL AUTH + ($retval) and return ( $retval, $retcard, $retuserid ); + return 0; + } + + # If we are in a shibboleth session (shibboleth is enabled, and a shibboleth match attribute is present) + # Check for password to asertain whether we want to be testing against shibboleth or another method this + # time around. + if ( $shib && $shib_login && !$password ) { + + $debug and print STDERR "## checkpw - checking Shibboleth\n"; + + # In case of a Shibboleth authentication, we expect a shibboleth user attribute + # (defined under shibboleth mapping in koha-conf.xml) to contain the login of the + # shibboleth-authenticated user + + # Then, we check if it matches a valid koha user + if ($shib_login) { + my ( $retval, $retcard, $retuserid ) = C4::Auth_with_shibboleth::checkpw_shib($shib_login); # EXTERNAL AUTH + ($retval) and return ( $retval, $retcard, $retuserid ); + return 0; + } } # INTERNAL AUTH + return checkpw_internal(@_) +} + +sub checkpw_internal { + my ( $dbh, $userid, $password ) = @_; + + $password = Encode::encode( 'UTF-8', $password ) + if Encode::is_utf8($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 2; + } + else { + return 0; + } + } + my $sth = $dbh->prepare( -"select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?" + "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where userid=?" ); $sth->execute($userid); if ( $sth->rows ) { - my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname, - $surname, $branchcode, $flags ) + my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname, + $surname, $branchcode, $branchname, $flags ) = $sth->fetchrow; - if ( md5_base64($password) eq $md5password and $md5password ne "!") { + + if ( checkpw_hash( $password, $stored_hash ) ) { C4::Context->set_userenv( "$borrowernumber", $userid, $cardnumber, - $firstname, $surname, $branchcode, $flags ); + $firstname, $surname, $branchcode, $branchname, $flags ); return 1, $cardnumber, $userid; } } $sth = $dbh->prepare( -"select password,cardnumber,borrowernumber,userid, firstname,surname,branchcode,flags from borrowers where cardnumber=?" + "select password,cardnumber,borrowernumber,userid,firstname,surname,borrowers.branchcode,branches.branchname,flags from borrowers join branches on borrowers.branchcode=branches.branchcode where cardnumber=?" ); $sth->execute($userid); if ( $sth->rows ) { - my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname, - $surname, $branchcode, $flags ) + my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname, + $surname, $branchcode, $branchname, $flags ) = $sth->fetchrow; - if ( md5_base64($password) eq $md5password ) { + + if ( checkpw_hash( $password, $stored_hash ) ) { C4::Context->set_userenv( $borrowernumber, $userid, $cardnumber, - $firstname, $surname, $branchcode, $flags ); + $firstname, $surname, $branchcode, $branchname, $flags ); return 1, $cardnumber, $userid; } } - if ( $userid && $userid eq C4::Context->config('user') - && "$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 2; - } - if ( $userid && $userid eq 'demo' + if ( $userid && $userid eq 'demo' && "$password" eq 'demo' && C4::Context->config('demo') ) { -# DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf -# some features won't be effective : modify systempref, modify MARC structure, + # DEMO => the demo user is allowed to do everything (if demo set to 1 in koha.conf + # some features won't be effective : modify systempref, modify MARC structure, return 2; } return 0; } +sub checkpw_hash { + my ( $password, $stored_hash ) = @_; + + return if $stored_hash eq '!'; + + # check what encryption algorithm was implemented: Bcrypt - if the hash starts with '$2' it is Bcrypt else md5 + my $hash; + if ( substr( $stored_hash, 0, 2 ) eq '$2' ) { + $hash = hash_password( $password, $stored_hash ); + } else { + $hash = md5_base64($password); + } + return $hash eq $stored_hash; +} + =head2 getuserflags my $authflags = getuserflags($flags, $userid, [$dbh]); @@ -1555,9 +1811,9 @@ C<$authflags> is a hashref of permissions =cut sub getuserflags { - my $flags = shift; - my $userid = shift; - my $dbh = @_ ? shift : C4::Context->dbh; + my $flags = shift; + my $userid = shift; + my $dbh = @_ ? shift : C4::Context->dbh; my $userflags; { # I don't want to do this, but if someone logs in as the database @@ -1580,8 +1836,8 @@ sub getuserflags { # get subpermissions and merge with top-level permissions my $user_subperms = get_user_subpermissions($userid); - foreach my $module (keys %$user_subperms) { - next if $userflags->{$module} == 1; # user already has permission for everything in this module + foreach my $module ( keys %$user_subperms ) { + next if $userflags->{$module} == 1; # user already has permission for everything in this module $userflags->{$module} = $user_subperms->{$module}; } @@ -1617,17 +1873,17 @@ sub get_user_subpermissions { my $userid = shift; my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("SELECT flag, user_permissions.code + my $sth = $dbh->prepare( "SELECT flag, user_permissions.code FROM user_permissions JOIN permissions USING (module_bit, code) JOIN userflags ON (module_bit = bit) JOIN borrowers USING (borrowernumber) - WHERE userid = ?"); + WHERE userid = ?" ); $sth->execute($userid); my $user_perms = {}; - while (my $perm = $sth->fetchrow_hashref) { - $user_perms->{$perm->{'flag'}}->{$perm->{'code'}} = 1; + while ( my $perm = $sth->fetchrow_hashref ) { + $user_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = 1; } return $user_perms; } @@ -1646,14 +1902,14 @@ of the subpermission. sub get_all_subpermissions { my $dbh = C4::Context->dbh; - my $sth = $dbh->prepare("SELECT flag, code, description + my $sth = $dbh->prepare( "SELECT flag, code, description FROM permissions - JOIN userflags ON (module_bit = bit)"); + JOIN userflags ON (module_bit = bit)" ); $sth->execute(); my $all_perms = {}; - while (my $perm = $sth->fetchrow_hashref) { - $all_perms->{$perm->{'flag'}}->{$perm->{'code'}} = $perm->{'description'}; + while ( my $perm = $sth->fetchrow_hashref ) { + $all_perms->{ $perm->{'flag'} }->{ $perm->{'code'} } = $perm->{'description'}; } return $all_perms; } @@ -1670,15 +1926,18 @@ Returns member's flags or 0 if a permission is not met. =cut sub haspermission { - my ($userid, $flagsrequired) = @_; + my ( $userid, $flagsrequired ) = @_; my $sth = C4::Context->dbh->prepare("SELECT flags FROM borrowers WHERE userid=?"); $sth->execute($userid); - my $flags = getuserflags($sth->fetchrow(), $userid); + my $row = $sth->fetchrow(); + my $flags = getuserflags( $row, $userid ); if ( $userid eq C4::Context->config('user') ) { + # Super User Account from /etc/koha.conf $flags->{'superlibrarian'} = 1; } elsif ( $userid eq 'demo' && C4::Context->config('demo') ) { + # Demo user that can do "anything" (demo=1 in /etc/koha.conf) $flags->{'superlibrarian'} = 1; } @@ -1687,26 +1946,28 @@ sub haspermission { foreach my $module ( keys %$flagsrequired ) { my $subperm = $flagsrequired->{$module}; - if ($subperm eq '*') { - return 0 unless ( $flags->{$module} == 1 or ref($flags->{$module}) ); + if ( $subperm eq '*' ) { + return 0 unless ( $flags->{$module} == 1 or ref( $flags->{$module} ) ); } else { - return 0 unless ( $flags->{$module} == 1 or - ( ref($flags->{$module}) and - exists $flags->{$module}->{$subperm} and - $flags->{$module}->{$subperm} == 1 - ) - ); + return 0 unless ( + ( defined $flags->{$module} and + $flags->{$module} == 1 ) + or + ( ref( $flags->{$module} ) and + exists $flags->{$module}->{$subperm} and + $flags->{$module}->{$subperm} == 1 ) + ); } } return $flags; + #FIXME - This fcn should return the failed permission so a suitable error msg can be delivered. } - sub getborrowernumber { my ($userid) = @_; my $userenv = C4::Context->userenv; - if ( defined( $userenv ) && ref( $userenv ) eq 'HASH' && $userenv->{number} ) { + if ( defined($userenv) && ref($userenv) eq 'HASH' && $userenv->{number} ) { return $userenv->{number}; } my $dbh = C4::Context->dbh; @@ -1722,7 +1983,6 @@ sub getborrowernumber { return 0; } - END { } # module clean-up code here (global destructor) 1; __END__ @@ -1733,6 +1993,8 @@ CGI(3) C4::Output(3) +Crypt::Eksblowfish::Bcrypt(3) + Digest::MD5(3) =cut