X-Git-Url: http://koha-dev.rot13.org:8081/gitweb/?a=blobdiff_plain;f=C4%2FAuth.pm;h=493c8dfc3db3300577d1946401443add6a665919;hb=fe1e6d86cadb9002c8e25d9787b0a7a0065c79dd;hp=44edf67ce230306fd5fefa65746d0e626754a7ba;hpb=08fe85950ae38a4faa5f0adce5e7d308a766117a;p=srvgit diff --git a/C4/Auth.pm b/C4/Auth.pm index 44edf67ce2..493c8dfc3d 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -20,21 +20,24 @@ package C4::Auth; use strict; use warnings; use Digest::MD5 qw(md5_base64); -use JSON qw/encode_json decode_json/; +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::Languages; use C4::Branch; # GetBranches +use C4::Search::History; use C4::VirtualShelves; 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 } @@ -49,17 +52,32 @@ BEGIN { @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 - ParseSearchHistorySession SetSearchHistorySession ); %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] ); $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); } + 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); } @@ -81,10 +99,10 @@ 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, + authnotrequired => 0, flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' }, } ); @@ -105,10 +123,10 @@ 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, + authnotrequired => 0, flagsrequired => {borrow => 1, catalogue => '*', tools => 'import_patrons' }, } ); @@ -129,16 +147,14 @@ Output.pm module. =cut -my $SEARCH_HISTORY_INSERT_SQL =<interface($in->{type}); + + $in->{'authnotrequired'} ||= 0; my $template = C4::Templates::gettemplate( $in->{'template_name'}, $in->{'type'}, @@ -174,6 +190,7 @@ sub get_template_and_user { # user info $template->param( loggedinusername => $user ); + $template->param( loggedinusernumber => $borrowernumber ); $template->param( sessionID => $sessionID ); my ($total, $pubshelves, $barshelves) = C4::VirtualShelves::GetSomeShelfNames($borrowernumber, 'MASTHEAD'); @@ -256,31 +273,54 @@ sub get_template_and_user { # If at least one search has already been performed if ($sth->fetchrow_array > 0) { - # We show the link in opac - $template->param(ShowOpacRecentSearchLink => 1); + # 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 = ParseSearchHistorySession($in->{'query'}); + my @recentSearches = C4::Search::History::get_from_session({ cgi => $in->{'query'} }); if (@recentSearches) { - my $sth = $dbh->prepare($SEARCH_HISTORY_INSERT_SQL); + 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'}, - $_->{'total'}, - $_->{'time'}, + $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 - SetSearchHistorySession($in->{'query'}, []); + 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... + # 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'); + } + } + $template->param( sessionID => $sessionID ); my ($total, $pubshelves) = C4::VirtualShelves::GetSomeShelfNames(undef, 'MASTHEAD'); @@ -292,9 +332,9 @@ sub get_template_and_user { # 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 = ParseSearchHistorySession($in->{'query'}); + my @recentSearches = C4::Search::History::get_from_session({ cgi => $in->{'query'} }); if (@recentSearches) { - $template->param(ShowOpacRecentSearchLink => 1); + $template->param(EnableOpacSearchHistory => 1); } } @@ -303,6 +343,12 @@ sub get_template_and_user { } # 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'), @@ -321,7 +367,7 @@ sub get_template_and_user { singleBranchMode => C4::Context->preference("singleBranchMode"), XSLTDetailsDisplay => C4::Context->preference("XSLTDetailsDisplay"), XSLTResultsDisplay => C4::Context->preference("XSLTResultsDisplay"), - using_https => $in->{'query'}->https() ? 1 : 0, + using_https => $using_https, noItemTypeImages => C4::Context->preference("noItemTypeImages"), marcflavour => C4::Context->preference("marcflavour"), persona => C4::Context->preference("persona"), @@ -380,13 +426,22 @@ sub get_template_and_user { 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+)/)){ + 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'}) { $opac_name = C4::Context->userenv->{'branch'}; } + # FIXME Under Plack the CGI->https method always returns 'OFF' ($using_https will be set to 0 in this case) + my $opac_base_url = C4::Context->preference("OPACBaseURL"); #FIXME uses $using_https below as well + if (!$opac_base_url){ + $opac_base_url = $ENV{'SERVER_NAME'} . ($ENV{'SERVER_PORT'} eq ($using_https ? "443" : "80") ? '' : ":$ENV{'SERVER_PORT'}"); + } $template->param( opaccolorstylesheet => C4::Context->preference("opaccolorstylesheet"), AnonSuggestions => "" . C4::Context->preference("AnonSuggestions"), @@ -404,11 +459,9 @@ sub get_template_and_user { 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'}"), + OPACBaseURL => ($using_https ? "https://" : "http://") . $opac_base_url, opac_css_override => $ENV{'OPAC_CSS_OVERRIDE'}, opac_search_limit => $opac_search_limit, opac_limit_override => $opac_limit_override, @@ -416,9 +469,6 @@ sub get_template_and_user { 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"), @@ -438,7 +488,6 @@ sub get_template_and_user { 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"), @@ -470,9 +519,9 @@ sub get_template_and_user { # 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 + # Extract the language, let C4::Languages::getlanguage choose # what to do - my $language = C4::Templates::getlanguage($in->{'query'},$in->{'type'}); + my $language = C4::Languages::getlanguage($in->{'query'}); my $languagecookie = C4::Templates::getlanguagecookie($in->{'query'},$language); if ( ref $cookie eq 'ARRAY' ) { push @{ $cookie }, $languagecookie; @@ -649,8 +698,18 @@ sub checkauth { my $casparam = $query->param('cas'); my $q_userid = $query->param('userid') // ''; - if ( $userid = $ENV{'REMOTE_USER'} ) { - # Using Basic Authentication, no cookies required + # 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 => '', @@ -676,7 +735,7 @@ sub checkauth { $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')); @@ -688,7 +747,7 @@ sub checkauth { $sessiontype = $session->param('sessiontype') || ''; } if ( ( $query->param('koha_login_context') && ($q_userid ne $s_userid) ) - || ( $cas && $query->param('ticket') ) ) { + || ( $cas && $query->param('ticket') ) || ( $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 = $q_userid but session id = $s_userid"; @@ -701,6 +760,8 @@ sub checkauth { } elsif ($logout) { # voluntary logout the user + # 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); @@ -708,9 +769,15 @@ sub checkauth { $sessionID = undef; $userid = undef; - if ($cas and $caslogout) { - logout_cas($query); - } + if ($cas and $caslogout) { + logout_cas($query); + } + + # 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 || ($lasttime < time() - $timeout) ) { # timed logout @@ -780,13 +847,26 @@ sub checkauth { } if ( ( $cas && $query->param('ticket') ) || $userid + || ( $shib && $shib_login ) || $pki_field ne 'None' || $persona ) { my $password = $query->param('password'); + my $shibSuccess = 0; my ( $return, $cardnumber ); - if ( $cas && $query->param('ticket') ) { + # 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; + # 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); + } + # 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 ); @@ -853,7 +933,8 @@ sub checkauth { ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query ); $userid = $retuserid if ( $retuserid ); - } + $info{'invalid_username_or_password'} = 1 unless ($return); + } } 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 ) ) { @@ -941,6 +1022,7 @@ sub checkauth { $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 ) { @@ -967,7 +1049,7 @@ sub checkauth { $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') ); } @@ -1024,7 +1106,7 @@ sub checkauth { $LibraryNameTitle =~ s/<(?:\/?)(?:br|p)\s*(?:\/?)>/ /sgi; $LibraryNameTitle =~ s/<(?:[^<>'"]|'(?:[^']*)'|"(?:[^"]*)")*>//sg; - my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tmpl' : 'auth.tmpl'; + my $template_name = ( $type eq 'opac' ) ? 'opac-auth.tt' : 'auth.tt'; my $template = C4::Templates::gettemplate($template_name, $type, $query ); $template->param( branchloop => GetBranchesLoop(), @@ -1033,6 +1115,7 @@ sub checkauth { login => 1, INPUTS => \@inputs, casAuthentication => C4::Context->preference("casAuthentication"), + shibbolethAuthentication => $shib, suggestion => C4::Context->preference("suggestion"), virtualshelves => C4::Context->preference("virtualshelves"), LibraryName => "" . C4::Context->preference("LibraryName"), @@ -1044,7 +1127,6 @@ sub checkauth { 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"), @@ -1104,6 +1186,13 @@ sub checkauth { ); } + if ($shib) { + $template->param( + shibbolethAuthentication => $shib, + shibbolethLoginUrl => login_shib_url($query), + ); + } + my $self_url = $query->url( -absolute => 1 ); $template->param( url => $self_url, @@ -1520,10 +1609,10 @@ 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 + return 0 if $retval == -1; # Incorrect password for LDAP login attempt ($retval) and return ($retval,$retcard,$retuserid); } @@ -1531,17 +1620,51 @@ sub checkpw { $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'); + $query->delete('ticket'); # remove ticket to come back to original URL my ($retval,$retcard,$retuserid) = checkpw_cas($dbh, $ticket, $query); # 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=?" @@ -1576,14 +1699,6 @@ sub checkpw_internal { 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' && "$password" eq 'demo' && C4::Context->config('demo') ) @@ -1791,27 +1906,6 @@ sub getborrowernumber { return 0; } -sub ParseSearchHistorySession { - my $cgi = shift; - my $sessionID = $cgi->cookie('CGISESSID'); - return () unless $sessionID; - my $session = get_session($sessionID); - return () unless $session and $session->param('search_history'); - my $obj = eval { decode_json(uri_unescape($session->param('search_history'))) }; - return () unless defined $obj; - return () unless ref $obj eq 'ARRAY'; - return @{ $obj }; -} - -sub SetSearchHistorySession { - my ($cgi, $search_history) = @_; - my $sessionID = $cgi->cookie('CGISESSID'); - return () unless $sessionID; - my $session = get_session($sessionID); - return () unless $session; - $session->param('search_history', uri_escape(encode_json($search_history))); -} - END { } # module clean-up code here (global destructor) 1; __END__