X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FAuth.pm;h=24e0bf1d1a1f38ca26e7a24ffabc3800de9bb5cb;hb=18cce456d9685fcf16936a18936873c142cb8aab;hp=b04df8b0410b5dfa7a7099710bb7d3d91f26a3ca;hpb=72f9f2239f9d71eec5e2cd987f445a25618d8027;p=koha_fer diff --git a/C4/Auth.pm b/C4/Auth.pm index b04df8b041..24e0bf1d1a 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -20,7 +20,7 @@ package C4::Auth; 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; @@ -28,8 +28,9 @@ require Exporter; use C4::Context; use C4::Templates; # to get the template use C4::Branch; # GetBranches -use C4::Update::Database; +use C4::Search::History; use C4::VirtualShelves; +use Koha::AuthUtils qw(hash_password); use POSIX qw/strftime/; use List::MoreUtils qw/ any /; @@ -39,23 +40,25 @@ use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug $ldap $cas $cas 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 $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); + @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 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 ($cas) { import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url); @@ -126,18 +129,20 @@ Output.pm module. =cut -my $SEARCH_HISTORY_INSERT_SQL =<{'template_name'}, $in->{'type'}, $in->{'query'} ); - my ( $user, $cookie, $sessionID, $flags, $new_session ); + my ( $user, $cookie, $sessionID, $flags ); + + 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, $new_session ) = checkauth( + ( $user, $cookie, $sessionID, $flags ) = checkauth( $in->{'query'}, $in->{'authnotrequired'}, $in->{'flagsrequired'}, @@ -146,8 +151,7 @@ sub get_template_and_user { } my $borrowernumber; - my $insecure = C4::Context->preference('insecure'); - if ($user or $insecure) { + 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 @@ -187,7 +191,7 @@ sub get_template_and_user { # 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) or $insecure==1) { + if ( $flags && $flags->{superlibrarian}==1 ) { $template->param( CAN_user_circulate => 1 ); $template->param( CAN_user_catalogue => 1 ); $template->param( CAN_user_parameters => 1 ); @@ -196,7 +200,7 @@ sub get_template_and_user { $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_updatecharges => 1 ); $template->param( CAN_user_acquisition => 1 ); $template->param( CAN_user_management => 1 ); $template->param( CAN_user_tools => 1 ); @@ -204,6 +208,8 @@ sub get_template_and_user { $template->param( CAN_user_serials => 1 ); $template->param( CAN_user_reports => 1 ); $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} }) { $template->param( "CAN_user_${module}_${subperm}" => 1 ); @@ -235,81 +241,67 @@ sub get_template_and_user { } } } - # 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; - my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?"; - 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); - } - - # And if there's a cookie with 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([]), - -expires => '' - ); - $cookie = [$cookie, $newsearchcookie]; - } - } - } + # 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; + my $query = "SELECT COUNT(*) FROM search_history WHERE userid=?"; + 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( EnableOpacSearchHistory => 1 ); + } + + # And if there are searches performed when the user was not logged in, + # we add them to the logged-in search history + 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 => [] }); + } + } } - else { # if this is an anonymous session, setup to display public lists... + 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, - ); + $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); - } - } - } + # 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')){ - if(C4::Context->preference('dateformat') eq "metric"){ - $template->param(dateformat_metric => 1); - } elsif(C4::Context->preference('dateformat') eq "us"){ - $template->param(dateformat_us => 1); - } else { - $template->param(dateformat_iso => 1); - } - } else { - $template->param(dateformat_iso => 1); + $template->param(dateformat => C4::Context->preference('dateformat')) } # these template parameters are set the same regardless of $in->{'type'} @@ -319,9 +311,11 @@ sub get_template_and_user { GoogleJackets => C4::Context->preference("GoogleJackets"), OpenLibraryCovers => C4::Context->preference("OpenLibraryCovers"), KohaAdminEmailAddress => "" . C4::Context->preference("KohaAdminEmailAddress"), - LoginBranchcode => (C4::Context->userenv?C4::Context->userenv->{"branch"}:"insecure"), + 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'), @@ -332,8 +326,8 @@ sub get_template_and_user { using_https => $in->{'query'}->https() ? 1 : 0, noItemTypeImages => C4::Context->preference("noItemTypeImages"), marcflavour => C4::Context->preference("marcflavour"), + persona => C4::Context->preference("persona"), ); - if ( $in->{'type'} eq "intranet" ) { $template->param( AmazonCoverImages => C4::Context->preference("AmazonCoverImages"), @@ -342,11 +336,11 @@ sub get_template_and_user { CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1, CircAutocompl => C4::Context->preference("CircAutocompl"), FRBRizeEditions => C4::Context->preference("FRBRizeEditions"), - IndependantBranches => C4::Context->preference("IndependantBranches"), + 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"}:"insecure"), + LoginBranchname => (C4::Context->userenv?C4::Context->userenv->{"branchname"}:undef), advancedMARCEditor => C4::Context->preference("advancedMARCEditor"), canreservefromotherbranches => C4::Context->preference('canreservefromotherbranches'), intranetcolorstylesheet => C4::Context->preference("intranetcolorstylesheet"), @@ -359,12 +353,13 @@ sub get_template_and_user { suggestion => C4::Context->preference("suggestion"), virtualshelves => C4::Context->preference("virtualshelves"), StaffSerialIssueDisplayCount => C4::Context->preference("StaffSerialIssueDisplayCount"), - NoZebra => C4::Context->preference('NoZebra'), 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"), ); } else { @@ -373,10 +368,15 @@ sub get_template_and_user { 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 + if ( C4::Context->preference("OpacBrowseResults") + && $in->{'template_name'} =~ /opac-(.+)\.(?:tt|tmpl)$/ ) { + my $pagename = $1; + unless ( $pagename =~ /^(?:MARC|ISBD)?detail$/ + or $pagename =~ /^addbybiblionumber$/ ) { + 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'}; @@ -384,6 +384,8 @@ sub get_template_and_user { 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. + } elsif ( $in->{'query'}->param('multibranchlimit') ) { + $opac_name = $in->{'query'}->param('multibranchlimit'); } elsif (C4::Context->preference("SearchMyLibraryFirst") && C4::Context->userenv && C4::Context->userenv->{'branch'}) { $opac_name = C4::Context->userenv->{'branch'}; } @@ -392,16 +394,16 @@ sub get_template_and_user { AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"), AuthorisedValueImages => C4::Context->preference("AuthorisedValueImages"), BranchesLoop => GetBranchesLoop($opac_name), - CalendarFirstDayOfWeek => (C4::Context->preference("CalendarFirstDayOfWeek") eq "Sunday")?0:1, + 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"), + OpacHighlightedWords => C4::Context->preference("OpacHighlightedWords"), OPACItemHolds => C4::Context->preference("OPACItemHolds"), OPACShelfBrowser => "". C4::Context->preference("OPACShelfBrowser"), - OpacShowRecentComments => C4::Context->preference("OpacShowRecentComments"), OPACURLOpenInNewWindow => "" . C4::Context->preference("OPACURLOpenInNewWindow"), OPACUserCSS => "". C4::Context->preference("OPACUserCSS"), OPACMobileUserCSS => "". C4::Context->preference("OPACMobileUserCSS"), @@ -409,7 +411,7 @@ sub get_template_and_user { 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_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, opac_search_limit => $opac_search_limit, opac_limit_override => $opac_limit_override, OpacBrowser => C4::Context->preference("OpacBrowser"), @@ -423,7 +425,7 @@ sub get_template_and_user { OpacNavRight => "" . C4::Context->preference("OpacNavRight"), OpacNavBottom => "" . C4::Context->preference("OpacNavBottom"), OpacPasswordChange => C4::Context->preference("OpacPasswordChange"), - OPACPatronDetails => C4::Context->preference("OPACPatronDetails"), + OPACPatronDetails => C4::Context->preference("OPACPatronDetails"), OPACPrivacy => C4::Context->preference("OPACPrivacy"), OPACFinesTab => C4::Context->preference("OPACFinesTab"), OpacTopissue => C4::Context->preference("OpacTopissue"), @@ -441,13 +443,11 @@ sub get_template_and_user { opacsmallimage => "" . C4::Context->preference("opacsmallimage"), opacuserjs => C4::Context->preference("opacuserjs"), opacuserlogin => "" . C4::Context->preference("opacuserlogin"), - reviewson => C4::Context->preference("reviewson"), 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"), - OpacAddMastheadLibraryPulldown => C4::Context->preference("OpacAddMastheadLibraryPulldown"), OPACXSLTDetailsDisplay => C4::Context->preference("OPACXSLTDetailsDisplay"), OPACXSLTResultsDisplay => C4::Context->preference("OPACXSLTResultsDisplay"), SyndeticsClientCode => C4::Context->preference("SyndeticsClientCode"), @@ -470,9 +470,17 @@ sub get_template_and_user { $template->param(OpacPublic => '1') if ($user || C4::Context->preference("OpacPublic")); } - if ( $new_session ) { - # Check the version and redirect if DB is not up-to-date - version_check($in->{query}, $in->{'type'}, $cookie); + # Check if we were asked using parameters to force a specific language + if ( defined $in->{'query'}->param('language') ) { + # Extract the language, let C4::Templates::getlanguage choose + # what to do + my $language = C4::Templates::getlanguage($in->{'query'},$in->{'type'}); + 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); @@ -556,6 +564,50 @@ has authenticated. =cut +sub _version_check { + 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') { + 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"); + } + 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=C4::Context::KOHAVERSION; + # 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=3"); + } else { + warn sprintf("OPAC: " . $warning, 'maintenance'); + print $query->redirect("/cgi-bin/koha/maintenance.pl"); + } + safe_exit; + } +} + sub _session_log { (@_) or return 0; open my $fh, '>>', "/tmp/sessionlog" or warn "ERROR: Cannot append to /tmp/sessionlog"; @@ -572,86 +624,61 @@ sub _timeout_syspref { return $timeout; } -sub version_check { - my ( $query, $type, $cookie ) = @_; - # check we have a Version. Otherwise => go to installer - unless ( C4::Context->preference('Version') ) { - if ( $type ne 'opac' ) { - $debug && warn "Install required, redirecting to Installer"; - print $query->redirect("/cgi-bin/koha/installer/install.pl"); - } else { - $debug && warn "OPAC Install required, redirecting to maintenance"; - print $query->redirect("/cgi-bin/koha/maintenance.pl"); - } - safe_exit; - } - - # check if you're uptodate, and if you're not, head to updater - my $koha39 = "3.0900028"; - - # Old updatedatabase method - if (C4::Context->preference('Version') < $koha39) { - print $query->redirect("/cgi-bin/koha/installer/install.pl?step=3"); - safe_exit; - } - - # New updatedatabase method - unless ( C4::Update::Database::is_uptodate() ) { - # not up-to-date, redirect to updatedatabase page - warn "redirect to updatedatabase"; - print $query->redirect(-location => "/cgi-bin/koha/admin/updatedatabase.pl", -cookie => $cookie); - safe_exit; - } -} - sub checkauth { my $query = shift; - $debug and warn "Checking Auth"; + $debug and warn "Checking Auth"; # $authnotrequired will be set for scripts which will run without authentication my $authnotrequired = shift; my $flagsrequired = shift; my $type = shift; + my $persona = shift; $type = 'opac' unless $type; - my $new_session = 0; my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); - # days - if ($timeout =~ /(\d+)[dD]/) { - $timeout = $1 * 86400; - }; - $timeout = 600 unless $timeout; + _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'); + my $q_userid = $query->param('userid') // ''; if ( $userid = $ENV{'REMOTE_USER'} ) { - # Using Basic Authentication, no cookies required + # Using Basic Authentication, no cookies required $cookie = $query->cookie( - -name => 'CGISESSID', - -value => '', - -expires => '' + -name => 'CGISESSID', + -value => '', + -expires => '', + -HttpOnly => 1, ); $loggedin = 1; } - elsif ( $sessionID = $query->cookie("CGISESSID")) { # assignment, not comparison + 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); + my $s_userid = ''; if ($session){ + $s_userid = $session->param('id') // ''; C4::Context::set_userenv( - $session->param('number'), $session->param('id'), + $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('emailaddress'), $session->param('branchprinter'), + $session->param('persona') ); C4::Context::set_shelves_userenv('bar',$session->param('barshelves')); C4::Context::set_shelves_userenv('pub',$session->param('pubshelves')); @@ -659,37 +686,41 @@ sub checkauth { $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'); + $userid = $s_userid; $sessiontype = $session->param('sessiontype') || ''; } - if ( ( ($query->param('koha_login_context')) && ($query->param('userid') ne $session->param('id')) ) + if ( ( $query->param('koha_login_context') && ($q_userid ne $s_userid) ) || ( $cas && $query->param('ticket') ) ) { #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; - } + $sessionID = undef; + $userid = undef; + } elsif ($logout) { # voluntary logout the user - $session->flush; $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); } - 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; @@ -701,14 +732,19 @@ sub checkauth { $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; } else { - $cookie = $query->cookie( CGISESSID => $session->id ); - $session->param('lasttime',time()); + $cookie = $query->cookie( + -name => 'CGISESSID', + -value => $session->id, + -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); if ($flags) { @@ -720,19 +756,37 @@ sub checkauth { } } 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( CGISESSID => $sessionID ); - - $userid = $query->param('userid'); + $cookie = $query->cookie( + -name => 'CGISESSID', + -value => $session->id, + -HttpOnly => 1 + ); + $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' ) + || $pki_field ne 'None' + || $persona ) { my $password = $query->param('password'); + my ( $return, $cardnumber ); if ( $cas && $query->param('ticket') ) { my $retuserid; @@ -740,9 +794,30 @@ sub checkauth { checkpw( $dbh, $userid, $password, $query ); $userid = $retuserid; $info{'invalidCasLogin'} = 1 unless ($return); - $new_session = 1; } - elsif ( + + 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 { + 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'} ) @@ -770,26 +845,26 @@ sub checkauth { } } - # 0 for no user, 1 for normal, 2 for demo user. + $return = $value ? 1 : 0; $userid = $value; - } + + } else { my $retuserid; ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query ); - $userid = $retuserid if ( $retuserid ne '' ); - $new_session = 1; - } - if ($return) { + $userid = $retuserid if ( $retuserid ); + } + 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 { - $info{'nopermission'} = 1; - C4::Context->_unset_userenv($sessionID); - } + if ( $flags = haspermission( $userid, $flagsrequired ) ) { + $loggedin = 1; + } + else { + $info{'nopermission'} = 1; + C4::Context->_unset_userenv($sessionID); + } my ($borrowernumber, $firstname, $surname, $userflags, $branchcode, $branchname, $branchprinter, $emailaddress); @@ -836,7 +911,7 @@ sub checkauth { $branchname = GetBranchName($branchcode); } my $branches = GetBranches(); - if (C4::Context->boolean_preference('IndependantBranches') && 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/){ @@ -885,43 +960,52 @@ sub checkauth { $session->param('ip',$session->remote_addr()); $session->param('lasttime',time()); } + if ($persona){ + $session->param('persona',1); + } 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('emailaddress'), $session->param('branchprinter'), + $session->param('persona') ); } - else { - if ($userid) { - $info{'invalid_username_or_password'} = 1; - C4::Context->_unset_userenv($sessionID); - } - } - } # END if ( $userid = $query->param('userid') ) - elsif ($type eq "opac") { + else { + if ($userid) { + $info{'invalid_username_or_password'} = 1; + C4::Context->_unset_userenv($sessionID); + } + $session->param('lasttime',time()); + $session->param('ip',$session->remote_addr()); + } + } # END if ( $userid = $query->param('userid') ) + 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..."; + $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'); - } - } # END unless ($userid) - my $insecure = C4::Context->boolean_preference('insecure'); + # setting a couple of other session vars... + $session->param('ip',$session->remote_addr()); + $session->param('lasttime',time()); + $session->param('sessiontype','anon'); + } + } # END unless ($userid) # finished authentification, now respond - if ( $loggedin || $authnotrequired || ( defined($insecure) && $insecure ) ) + if ( $loggedin || $authnotrequired ) { # successful login unless ($cookie) { - $cookie = $query->cookie( CGISESSID => '' ); + $cookie = $query->cookie( + -name => 'CGISESSID', + -value => '', + -HttpOnly => 1 + ); } - return ( $userid, $cookie, $sessionID, $flags, $new_session ); + return ( $userid, $cookie, $sessionID, $flags ); } # @@ -938,6 +1022,10 @@ sub checkauth { push @inputs, { name => $name, value => $value }; } + my $LibraryNameTitle = C4::Context->preference("LibraryName"); + $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 ); $template->param( @@ -949,7 +1037,8 @@ sub checkauth { casAuthentication => C4::Context->preference("casAuthentication"), suggestion => C4::Context->preference("suggestion"), virtualshelves => C4::Context->preference("virtualshelves"), - LibraryName => C4::Context->preference("LibraryName"), + LibraryName => "" . C4::Context->preference("LibraryName"), + LibraryNameTitle => "" . $LibraryNameTitle, opacuserlogin => C4::Context->preference("opacuserlogin"), OpacNav => C4::Context->preference("OpacNav"), OpacNavRight => C4::Context->preference("OpacNavRight"), @@ -974,36 +1063,45 @@ sub checkauth { IntranetNav => C4::Context->preference("IntranetNav"), IntranetFavicon => C4::Context->preference("IntranetFavicon"), intranetuserjs => C4::Context->preference("intranetuserjs"), - IndependantBranches=> C4::Context->preference("IndependantBranches"), + 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'}, ); $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'); + $template->param( + pubshelves => $total->{pubtotal}, + pubshelvesloop => $pubshelves, + ); + } + if ($cas) { - # Is authentication against multiple CAS servers enabled? + # 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" }; - } - #warn Data::Dumper::Dumper(\@tmplservers); - $template->param( - casServersLoop => \@tmplservers - ); - } else { + 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" }; + } + $template->param( + casServersLoop => \@tmplservers + ); + } else { $template->param( casServerUrl => login_cas_url($query), - ); - } + ); + } - $template->param( + $template->param( invalidCasLogin => $info{'invalidCasLogin'} ); } @@ -1066,6 +1164,19 @@ sub check_api_auth { my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); + unless (C4::Context->preference('Version')) { + # database has not been installed yet + return ("maintenance", undef, undef); + } + my $kohaversion=C4::Context::KOHAVERSION; + $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; + 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); + } + # FIXME -- most of what follows is a copy-and-paste # of code from checkauth. There is an obvious need # for refactoring to separate the various parts of @@ -1099,6 +1210,7 @@ sub check_api_auth { if ( $lasttime < time() - $timeout ) { # time out $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; @@ -1106,18 +1218,24 @@ sub check_api_auth { } elsif ( $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); } else { - my $cookie = $query->cookie( CGISESSID => $session->id ); + my $cookie = $query->cookie( + -name => 'CGISESSID', + -value => $session->id, + -HttpOnly => 1, + ); $session->param('lasttime',time()); my $flags = haspermission($userid, $flagsrequired); if ($flags) { return ("ok", $cookie, $sessionID); } else { $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; @@ -1131,23 +1249,23 @@ sub check_api_auth { # new login my $userid = $query->param('userid'); my $password = $query->param('password'); - 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); - } - ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query ); - } + 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); + } + ( $return, $cardnumber ) = checkpw( $dbh, $userid, $password, $query ); + } if ($return and haspermission( $userid, $flagsrequired)) { my $session = get_session(""); @@ -1155,7 +1273,11 @@ sub check_api_auth { my $sessionID = $session->id; C4::Context->_new_userenv($sessionID); - my $cookie = $query->cookie(CGISESSID => $sessionID); + my $cookie = $query->cookie( + -name => 'CGISESSID', + -value => $sessionID, + -HttpOnly => 1, + ); if ( $return == 1 ) { my ( $borrowernumber, $firstname, $surname, @@ -1285,6 +1407,19 @@ sub check_cookie_auth { my $dbh = C4::Context->dbh; my $timeout = _timeout_syspref(); + unless (C4::Context->preference('Version')) { + # database has not been installed yet + return ("maintenance", undef); + } + my $kohaversion=C4::Context::KOHAVERSION; + $kohaversion =~ s/(.*\..*)\.(.*)\.(.*)/$1$2$3/; + 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); + } + # FIXME -- most of what follows is a copy-and-paste # of code from checkauth. There is an obvious need # for refactoring to separate the various parts of @@ -1317,6 +1452,7 @@ sub check_cookie_auth { if ( $lasttime < time() - $timeout ) { # time out $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; @@ -1324,6 +1460,7 @@ sub check_cookie_auth { } elsif ( $ip ne $ENV{'REMOTE_ADDR'} ) { # IP address changed $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; @@ -1335,6 +1472,7 @@ sub check_cookie_auth { return ("ok", $sessionID); } else { $session->delete(); + $session->flush; C4::Context->_unset_userenv($sessionID); $userid = undef; $sessionID = undef; @@ -1373,7 +1511,7 @@ sub get_session { $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 } ); + $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 @@ -1383,8 +1521,8 @@ sub get_session { } sub checkpw { - my ( $dbh, $userid, $password, $query ) = @_; + if ($ldap) { $debug and print STDERR "## checkpw - checking LDAP\n"; my ($retval,$retcard,$retuserid) = checkpw_ldap(@_); # EXTERNAL AUTH @@ -1393,24 +1531,30 @@ sub checkpw { 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'); + # 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; + return 0; } - # INTERNAL AUTH + return checkpw_internal(@_) +} + +sub checkpw_internal { + my ( $dbh, $userid, $password ) = @_; + my $sth = $dbh->prepare( "select password,cardnumber,borrowernumber,userid,firstname,surname,branchcode,flags from borrowers where userid=?" ); $sth->execute($userid); if ( $sth->rows ) { - my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname, + my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname, $surname, $branchcode, $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 ); @@ -1423,10 +1567,11 @@ sub checkpw { ); $sth->execute($userid); if ( $sth->rows ) { - my ( $md5password, $cardnumber, $borrowernumber, $userid, $firstname, + my ( $stored_hash, $cardnumber, $borrowernumber, $userid, $firstname, $surname, $branchcode, $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 ); @@ -1453,6 +1598,21 @@ sub checkpw { 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]); @@ -1488,7 +1648,6 @@ sub getuserflags { $userflags->{$flag} = 0; } } - # get subpermissions and merge with top-level permissions my $user_subperms = get_user_subpermissions($userid); foreach my $module (keys %$user_subperms) { @@ -1584,7 +1743,8 @@ sub haspermission { 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; @@ -1633,7 +1793,6 @@ sub getborrowernumber { return 0; } - END { } # module clean-up code here (global destructor) 1; __END__ @@ -1644,6 +1803,8 @@ CGI(3) C4::Output(3) +Crypt::Eksblowfish::Bcrypt(3) + Digest::MD5(3) =cut