From 3c9004357d48944ce36d3964f7e6f1ba15702b71 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Tue, 1 Apr 2014 12:15:40 +0000 Subject: [PATCH] BUG8446, Follow up: Improve local login fallback - Local fallback was not very well implemented, this patch adds better handling for such cases allowing clearer failure messages - This patch also adds the ability to use single sign on via the top bar menu in the bootstrap theme. BUG8446, Follow up: Adds perldoc documentation - Add some documentation to the Auth_with_Shibboleth module including some guidance as to configuration. BUG8446, Follow up: Correct filenames to match guidlines - Moved Auth_with_Shibboleth.pm to Auth_with_shibboleth.pm to match other files present on the system. BUG8446, Follow up: Correct paths after file rename BUG8446, Follow up: Implemented single sign out - This follow up rebases the code against 3.16+ which managed to break some of the original logic. - As a side effect of the rebasing, we've also implemented the single sign out element. Upon logout, koha will request that the shibboleth session is destroyed, and then clear the local koha session upon return to koha. Due to the nature of shibboleth however, you will only truly be signed out of the IdP if they properly support Single Sign Out (which many do not). As a consequence, although you may appear to be logged out in koha, you might find that upon clicking 'login' the IdP does NOT request your login details again, but instead logs you silently back into your koha session. This is NOT a koha bug, but a shibboleth implementation issue that is well known. BUG8446, Follow up: Fixed bootstrap login via modal - The bootstrap theme enable login from any opac page via modal. To enable this with shibboleth we had to make some template parameters globally accessible when shibboleth is enabled. BUG8446, Follow up: Add template rules for Shibboleth and CAS - Add template rules so that CAS and Shibboleth can coexist. BUG8446, Follow up: Added default config to config file BUG8446, Follow up: Embellished perldoc documentation - Updated perldoc to correct detail about configuring shibboleth authentication. - Updated perldoc to include subroutines and their respective functions. BUG8446, Follow up: Enable configuration of match field - Added clearer, more flexible, configuration of shibboleth attribute to koha borrower field matching for authentication - Correcting of documentation to make it more clear to the current implementation - Minor refactoring of code to reduce some code duplication Signed-off-by: Matthias Meusburger Signed-off-by: Katrin Fischer Signed-off-by: Tomas Cohen Arazi --- C4/Auth.pm | 66 ++++-- C4/Auth_with_Shibboleth.pm | 107 --------- C4/Auth_with_shibboleth.pm | 222 ++++++++++++++++++ C4/Context.pm | 4 +- etc/koha-conf.xml | 1 + .../bootstrap/en/includes/masthead.inc | 12 + .../bootstrap/en/modules/opac-auth.tt | 56 ++++- opac/opac-main.pl | 2 - 8 files changed, 331 insertions(+), 139 deletions(-) delete mode 100644 C4/Auth_with_Shibboleth.pm create mode 100644 C4/Auth_with_shibboleth.pm diff --git a/C4/Auth.pm b/C4/Auth.pm index 552a01884c..c38499294e 100644 --- a/C4/Auth.pm +++ b/C4/Auth.pm @@ -58,14 +58,16 @@ BEGIN { $shib = C4::Context->config('useshibboleth') || 0; $caslogout = C4::Context->preference('casLogout'); require C4::Auth_with_cas; # no import - require C4::Auth_with_Shibboleth; + require C4::Auth_with_shibboleth; if ($ldap) { require C4::Auth_with_ldap; import C4::Auth_with_ldap qw(checkpw_ldap); } if ($shib) { - import C4::Auth_with_Shibboleth qw(checkpw_shib logout_shib login_shib_url get_login_shib); - # Getting user login + import C4::Auth_with_shibboleth + qw(checkpw_shib logout_shib login_shib_url get_login_shib); + + # Get shibboleth login attribute $shib_login = get_login_shib(); } if ($cas) { @@ -296,6 +298,20 @@ sub get_template_and_user { } 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'); @@ -713,7 +729,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')); @@ -725,7 +741,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"; @@ -738,6 +754,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); @@ -749,8 +767,8 @@ sub checkauth { logout_cas($query); } - # If we are in a shibboleth session (shibboleth is enabled, and a shibboleth username is set) - if ( $shib and $shib_login and $type eq 'opac') { + # 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); } @@ -823,20 +841,26 @@ sub checkauth { } if ( ( $cas && $query->param('ticket') ) || $userid - || $shib + || ( $shib && $shib_login ) || $pki_field ne 'None' || $persona ) { my $password = $query->param('password'); + my $shibSuccess = 0; my ( $return, $cardnumber ); - if ($shib && $shib_login && $type eq 'opac' && !$password) { + # 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; - ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query ); + # 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 ( $cas && $query->param('ticket') ) { + } + # 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 ); @@ -903,7 +927,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 ) ) { @@ -991,6 +1016,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 ) { @@ -1017,7 +1043,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') ); } @@ -1156,6 +1182,7 @@ sub checkauth { if ($shib) { $template->param( + shibbolethAuthentication => $shib, shibbolethLoginUrl => login_shib_url($query), ); } @@ -1576,7 +1603,6 @@ 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 @@ -1594,17 +1620,19 @@ sub checkpw { return 0; } - # If we are in a shibboleth session (shibboleth is enabled and no password has been provided) - if ($shib && !$password) { + # 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 in the shibbolethLoginAttribute) tto contain the login of the + # (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( $dbh, $shib_login ); # EXTERNAL AUTH + my ( $retval, $retcard, $retuserid ) = C4::Auth_with_shibboleth::checkpw_shib( $dbh, $shib_login ); # EXTERNAL AUTH ($retval) and return ( $retval, $retcard, $retuserid ); return 0; } diff --git a/C4/Auth_with_Shibboleth.pm b/C4/Auth_with_Shibboleth.pm deleted file mode 100644 index 5c6c6b3ebd..0000000000 --- a/C4/Auth_with_Shibboleth.pm +++ /dev/null @@ -1,107 +0,0 @@ -package C4::Auth_with_Shibboleth; - -# Copyright 2011 BibLibre -# -# 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 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. - -use strict; -use warnings; - -use C4::Debug; -use C4::Context; -use Carp; -use CGI; - -use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug); - -BEGIN { - require Exporter; - $VERSION = 3.03; # set the version for version checking - $debug = $ENV{DEBUG}; - @ISA = qw(Exporter); - @EXPORT = qw(logout_shib login_shib_url checkpw_shib get_login_shib); -} -my $context = C4::Context->new() or die 'C4::Context->new failed'; -my $protocol = "https://"; - -# Logout from Shibboleth -sub logout_shib { - my ($query) = @_; - my $uri = $protocol . C4::Context->preference('OPACBaseURL'); - print $query->redirect( $uri . "/Shibboleth.sso/Logout?return=$uri" ); -} - -# Returns Shibboleth login URL with callback to the requesting URL -sub login_shib_url { - - my ($query) = @_; - my $param = $protocol . C4::Context->preference('OPACBaseURL') . $query->script_name(); - if ( $query->query_string() ) { - $param = $param . '%3F' . $query->query_string(); - } - my $uri = $protocol . C4::Context->preference('OPACBaseURL') . "/Shibboleth.sso/Login?target=$param"; - return $uri; -} - -# Returns shibboleth user login -sub get_login_shib { - - # In case of a Shibboleth authentication, we expect a shibboleth user attribute (defined in the shibbolethLoginAttribute) - # to contain the login of the shibboleth-authenticated user - - # Shibboleth attributes are mapped into http environmement variables, - # so we're getting the login of the user this way - - my $shib = C4::Context->config('shibboleth') or croak 'No in koha-conf.xml'; - - my $shibbolethLoginAttribute = $shib->{'userid'}; - $debug and warn "shibboleth->userid value: $shibbolethLoginAttribute"; - $debug and warn "$shibbolethLoginAttribute value: " . $ENV{$shibbolethLoginAttribute}; - - return $ENV{$shibbolethLoginAttribute} || ''; -} - -# Checks for password correctness -# In our case : does the given username matches one of our users ? -sub checkpw_shib { - $debug and warn "checkpw_shib"; - - my ( $dbh, $userid ) = @_; - my $retnumber; - $debug and warn "User Shibboleth-authenticated as: $userid"; - - my $shib = C4::Context->config('shibboleth') or croak 'No in koha-conf.xml'; - - # Does it match one of our users ? - my $sth = $dbh->prepare("select cardnumber from borrowers where userid=?"); - $sth->execute($userid); - if ( $sth->rows ) { - $retnumber = $sth->fetchrow; - return ( 1, $retnumber, $userid ); - } - $sth = $dbh->prepare("select userid from borrowers where cardnumber=?"); - $sth->execute($userid); - if ( $sth->rows ) { - $retnumber = $sth->fetchrow; - return ( 1, $retnumber, $userid ); - } - - # If we reach this point, the user is not a valid koha user - $debug and warn "User $userid is not a valid Koha user"; - return 0; -} - -1; diff --git a/C4/Auth_with_shibboleth.pm b/C4/Auth_with_shibboleth.pm new file mode 100644 index 0000000000..18538ab6ee --- /dev/null +++ b/C4/Auth_with_shibboleth.pm @@ -0,0 +1,222 @@ +package C4::Auth_with_shibboleth; + +# Copyright 2011 BibLibre +# +# 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 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. + +use strict; +use warnings; + +use C4::Debug; +use C4::Context; +use Carp; +use CGI; + +use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $debug); + +BEGIN { + require Exporter; + $VERSION = 3.03; # set the version for version checking + $debug = $ENV{DEBUG}; + @ISA = qw(Exporter); + @EXPORT = qw(logout_shib login_shib_url checkpw_shib get_login_shib); +} +my $context = C4::Context->new() or die 'C4::Context->new failed'; +my $shib = C4::Context->config('shibboleth') or croak 'No in koha-conf.xml'; +my $shibbolethMatchField = $shib->{matchpoint} or croak 'No defined in koha-conf.xml'; +my $shibbolethMatchAttribute = $shib->{mapping}->{$shibbolethMatchField}->{is} or croak 'Matchpoint not mapped in koha-conf.xml'; +my $protocol = "https://"; + +# Logout from Shibboleth +sub logout_shib { + my ($query) = @_; + my $uri = $protocol . C4::Context->preference('OPACBaseURL'); + print $query->redirect( $uri . "/Shibboleth.sso/Logout?return=$uri" ); +} + +# Returns Shibboleth login URL with callback to the requesting URL +sub login_shib_url { + + my ($query) = @_; + my $param = $protocol . C4::Context->preference('OPACBaseURL') . $query->script_name(); + if ( $query->query_string() ) { + $param = $param . '%3F' . $query->query_string(); + } + my $uri = $protocol . C4::Context->preference('OPACBaseURL') . "/Shibboleth.sso/Login?target=$param"; + return $uri; +} + +# Returns shibboleth user login +sub get_login_shib { + + # In case of a Shibboleth authentication, we expect a shibboleth user attribute + # to contain the login match point of the shibboleth-authenticated user. This match + # point is configured in koha-conf.xml + + # Shibboleth attributes are mapped into http environmement variables, so we're getting + # the match point of the user this way + + $debug and warn "koha borrower field to match: $shibbolethMatchField"; + $debug and warn "shibboleth attribute to match: $shibbolethMatchAttribute"; + $debug and warn "$shibbolethMatchAttribute value: $ENV{$shibbolethMatchAttribute}"; + + return $ENV{$shibbolethMatchAttribute} || ''; +} + +# Checks for password correctness +# In our case : does the given attribute match one of our users ? +sub checkpw_shib { + $debug and warn "checkpw_shib"; + + my ( $dbh, $userid ) = @_; + my $retnumber; + $debug and warn "User Shibboleth-authenticated as: $userid"; + + # Does the given shibboleth attribute value ($userid) match a valid koha user ? + my $sth = $dbh->prepare("select cardnumber, userid from borrowers where $shibbolethMatchField=?"); + $sth->execute($userid); + if ( $sth->rows ) { + my @retvals = $sth->fetchrow; + $retnumber = $retvals[1]; + $userid = $retvals[0]; + return ( 1, $retnumber, $userid ); + } + + # If we reach this point, the user is not a valid koha user + $debug and warn "User $userid is not a valid Koha user"; + return 0; +} + +1; +__END__ + +=head1 NAME + +C4::Auth_with_shibboleth + +=head1 SYNOPSIS + +use C4::Auth_with_shibboleth; + +=head1 DESCRIPTION + +This module is specific to Shibboleth authentication in koha and relies heavily upon the native shibboleth service provider package in your operating system. + +=head1 CONFIGURATION + +To use this type of authentication these additional packages are required: + +=over + +=item * + +libapache2-mod-shib2 + +=item * + +libshibsp5:amd64 + +=item * + +shibboleth-sp2-schemas + +=back + +We let the native shibboleth service provider packages handle all the complexities of shibboleth negotiation for us, and configuring this is beyond the scope of this documentation. + +But to sum up, to get shibboleth working in koha, as a minimum you will need to: + +=over + +=item 1. + +Create some metadata for your koha instance (if you're in a single instance setup then the default metadata available at https://youraddress.com/Shibboleth.sso/Metadata should be adequate) + +=item 2. + +Swap metadata with your Identidy Provider (IdP) + +=item 3. + +Map their attributes to what you want to see in koha + +=item 4. + +Tell apache that we wish to allow koha to authenticate via shibboleth. + +This is as simple as adding the below to your virtualhost config: + + + AuthType shibboleth + Require shibboleth + + +=item 5. + +Configure koha to listen for shibboleth environment variables. + +This is as simple as enabling B in koha-conf.xml: + + 1 + +=item 6. + +Map shibboleth attributes to koha fields, and configure authentication match point in koha-conf.xml. + + + userid + + + + + +Note: The minimum you need here is a block, containing a valid column name from the koha borrowers table, and a block containing a relation between the chosen matchpoint and the shibboleth attribute name. + +=back + +It should be as simple as that; you should now be able to login via shibboleth in the opac. + +If you need more help configuring your Bervice B

rovider to authenticate against a chosen Bentity B

rovider then it might be worth taking a look at the community wiki L + +=head1 FUNCTIONS + +=head2 logout_shib + +Sends a logout signal to the native shibboleth service provider and then logs out of koha. Depending upon the native service provider configuration and identity provider capabilities this may or may not perform a single sign out action. + + logout_shib($query); + +=head2 login_shib_url + +Given a query, this will return a shibboleth login url with return code to page with given given query. + + my $shibLoginURL = login_shib_url($query); + +=head2 get_login_shib + +Returns the shibboleth login attribute should it be found present in the http session + + my $shib_login = get_login_shib(); + +=head2 checkpw_shib + +Given a database handle and a shib_login attribute, this routine checks for a matching local user and if found returns true, their cardnumber and their userid. If a match is not found, then this returns false. + + my ( $retval, $retcard, $retuserid ) = C4::Auth_with_shibboleth::checkpw_shib( $dbh, $shib_login ); + +=head1 SEE ALSO + +=cut diff --git a/C4/Context.pm b/C4/Context.pm index 63e8ebe277..6f774662a7 100644 --- a/C4/Context.pm +++ b/C4/Context.pm @@ -1114,7 +1114,7 @@ set_userenv is called in Auth.pm #' sub set_userenv { - my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $userflags, $emailaddress, $branchprinter, $persona)= @_; + my ($usernum, $userid, $usercnum, $userfirstname, $usersurname, $userbranch, $branchname, $userflags, $emailaddress, $branchprinter, $persona, $shibboleth)= @_; my $var=$context->{"activeuser"} || ''; my $cell = { "number" => $usernum, @@ -1129,6 +1129,7 @@ sub set_userenv { "emailaddress" => $emailaddress, "branchprinter" => $branchprinter, "persona" => $persona, + "shibboleth" => $shibboleth, }; $context->{userenv}->{$var} = $cell; return $cell; @@ -1302,4 +1303,3 @@ Andrew Arensburger Joshua Ferraro -=cut diff --git a/etc/koha-conf.xml b/etc/koha-conf.xml index 0392fb8943..36e4f71d44 100644 --- a/etc/koha-conf.xml +++ b/etc/koha-conf.xml @@ -109,6 +109,7 @@ __PAZPAR2_TOGGLE_XML_POST__ http://__PAZPAR2_HOST__:__PAZPAR2_PORT__/search.pz2 __MISC_DIR__/koha-install-log 0 + 0 __BIB_INDEX_MODE__ __AUTH_INDEX_MODE__ __ZEBRA_LOCK_DIR__ diff --git a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc index 2ee0ed99d5..43e0cd886d 100644 --- a/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc +++ b/koha-tmpl/opac-tmpl/bootstrap/en/includes/masthead.inc @@ -281,6 +281,18 @@