@@ -, +, @@ - Use the shibbolethAuthentication syspref to enable Shibboleth authentication - Configure the shibbolethLoginAttribute to specify which shibboleth user attribute matches the koha login - Make sure the OPACBaseURL is correctly set - Fix logout bug: shibboleth logout now occurs only when the session is a shibboleth one. - Do some refactoring: getting shibboleth username is now done in C4::Auth_with_Shibboleth.pm (get_login_shib function) - Adds redirect to opac after logout - Shibboleth is not compatible with basic http authentication in C4/Auth.pm. This patch fixes that. - Use ENV{'SERVER_NAME'} instead of syspref OpacBaseURL in order to work with multiple vhosts. - Adds missing protocol for $ENV{'SERVER_NAME'} --- C4/Auth.pm | 75 +++++++++++++-- C4/Auth_with_Shibboleth.pm | 100 ++++++++++++++++++++ installer/data/mysql/updatedatabase.pl | 8 ++ .../prog/en/modules/admin/preferences/admin.pref | 11 +++ koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt | 16 ++++ koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt | 1 + opac/opac-main.pl | 2 + opac/opac-user.pl | 3 + 8 files changed, 208 insertions(+), 8 deletions(-) create mode 100644 C4/Auth_with_Shibboleth.pm --- a/C4/Auth.pm +++ a/C4/Auth.pm @@ -36,7 +36,7 @@ use POSIX qw/strftime/; use List::MoreUtils qw/ any /; # 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 } @@ -55,12 +55,19 @@ BEGIN { %EXPORT_TAGS = ( EditPermissions => [qw(get_all_subpermissions get_user_subpermissions)] ); $ldap = C4::Context->config('useldapserver') || 0; $cas = C4::Context->preference('casAuthentication'); + $shib = C4::Context->preference('shibbolethAuthentication'); $caslogout = C4::Context->preference('casLogout'); require C4::Auth_with_cas; # no import + 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 + $shib_login = get_login_shib(); + } if ($cas) { import C4::Auth_with_cas qw(check_api_auth_cas checkpw_cas login_cas logout_cas login_cas_url); } @@ -666,8 +673,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 $ENV{'REMOTE_USER'} ne '' and $userid = $ENV{'REMOTE_USER'} ) { + + # Using Basic Authentication, no cookies required $cookie = $query->cookie( -name => 'CGISESSID', -value => '', @@ -725,9 +742,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, and a shibboleth username is set) + if ( $shib and $shib_login 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 @@ -797,13 +820,20 @@ sub checkauth { } if ( ( $cas && $query->param('ticket') ) || $userid + || $shib || $pki_field ne 'None' - || $persona ) + || $persona ) { my $password = $query->param('password'); my ( $return, $cardnumber ); - if ( $cas && $query->param('ticket') ) { + if ($shib && $shib_login && $type eq 'opac' && !$password) { + my $retuserid; + ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query ); + $userid = $retuserid; + $info{'invalidShibLogin'} = 1 unless ($return); + + } elsif ( $cas && $query->param('ticket') ) { my $retuserid; ( $return, $cardnumber, $retuserid ) = checkpw( $dbh, $userid, $password, $query ); @@ -1050,6 +1080,7 @@ sub checkauth { login => 1, INPUTS => \@inputs, casAuthentication => C4::Context->preference("casAuthentication"), + shibbolethAuthentication => C4::Context->preference("shibbolethAuthentication"), suggestion => C4::Context->preference("suggestion"), virtualshelves => C4::Context->preference("virtualshelves"), LibraryName => "" . C4::Context->preference("LibraryName"), @@ -1121,6 +1152,12 @@ sub checkauth { ); } + if ($shib) { + $template->param( + shibbolethLoginUrl => login_shib_url($query), + ); + } + my $self_url = $query->url( -absolute => 1 ); $template->param( url => $self_url, @@ -1554,6 +1591,28 @@ sub checkpw { return 0; } + # If we are in a shibboleth session (shibboleth is enabled and no password has been provided) + if ($shib && !$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 + # shibboleth-authenticated user + + # Shibboleth attributes are mapped into http environmement variables, + # so we're getting the login of the user this way + my $attributename = C4::Context->preference('shibbolethLoginAttribute'); + my $attributevalue = $ENV{$attributename}; + + # 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 + ($retval) and return ( $retval, $retcard, $retuserid ); + return 0; + } + } + + # INTERNAL AUTH return checkpw_internal(@_) } --- a/C4/Auth_with_Shibboleth.pm +++ a/C4/Auth_with_Shibboleth.pm @@ -0,0 +1,100 @@ +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 C4::Utils qw( :all ); +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 . $ENV{'SERVER_NAME'}; + 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 . $ENV{'SERVER_NAME'} . $query->script_name(); + my $uri = $protocol . $ENV{'SERVER_NAME'} . "/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 $shibbolethLoginAttribute = C4::Context->preference('shibbolethLoginAttribute'); + $debug and warn "shibbolethLoginAttribute 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"; + + # 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; --- a/installer/data/mysql/updatedatabase.pl +++ a/installer/data/mysql/updatedatabase.pl @@ -8560,6 +8560,14 @@ if ( CheckVersion($DBversion) ) { SetVersion($DBversion); } +$DBversion = "XXX"; +if (C4::Context->preference("Version") < TransformToNum($DBversion)) { + $dbh->do("INSERT INTO `systempreferences` (variable,value,options,explanation,type) VALUES('shibbolethAuthentication','','','Enable or disable Shibboleth authentication','YesNo')"); + $dbh->do("INSERT INTO `systempreferences` (variable,value,options,explanation,type) VALUES('shibbolethLoginAttribute','','','Which shibboleth user attribute should be used to match koha user login?','')"); + print "Upgrade to $DBversion done (Adds shibbolethAuthentication and shibbolethLoginAttribute preferences)\n"; + SetVersion ($DBversion); +} + =head1 FUNCTIONS =head2 TableExists($table) --- a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref +++ a/koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref @@ -105,6 +105,17 @@ Administration: yes: Allow no: "Don't Allow" - Mozilla persona for login + Shibboleth Authentication: + - + - pref: shibbolethAuthentication + default: 0 + choices: + yes: Use + no: "Don't use" + - Shibboleth for login authentication. + - + - Which shibboleth user attribute should be used to match koha user login? + - pref: shibbolethLoginAttribute Search Engine: - - pref: SearchEngine --- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt +++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt @@ -46,6 +46,22 @@

You entered an incorrect username or password. Please try again! And remember, usernames and passwords are case sensitive.

[% END %] +[% IF ( shibbolethAuthentication ) %] +

Shibboleth Login

+ +[% IF ( invalidShibLogin ) %] + +

Sorry, the shibboleth login failed.

+[% END %] + +

If you have a shibboleth account, +please click here to login.

+ +

Local Login

+

If you do not have a shibboleth account, but a local account, you can still log in :

+ +[% END %] + [% IF ( casAuthentication ) %]

Cas login

--- a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt +++ a/koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt @@ -47,6 +47,7 @@ [% IF ( opacuserlogin ) %] [% UNLESS ( loggedinusername ) %] [% UNLESS ( casAuthentication ) %] + [% UNLESS ( shibbolethAuthentication ) %]
--- a/opac/opac-main.pl +++ a/opac/opac-main.pl @@ -44,6 +44,8 @@ $template->param( casAuthentication => $casAuthentication, ); +my $shibbolethAuthentication = C4::Context->preference('shibbolethAuthentication'); +$template->param( shibbolethAuthentication => $shibbolethAuthentication); # display news # use cookie setting for language, bug default to syspref if it's not set --- a/opac/opac-user.pl +++ a/opac/opac-user.pl @@ -72,6 +72,9 @@ for ( C4::Context->preference("OPACShowHoldQueueDetails") ) { my $patronupdate = $query->param('patronupdate'); my $canrenew = 1; +my $shibbolethAuthentication = C4::Context->preference('shibbolethAuthentication'); +$template->param( shibbolethAuthentication => $shibbolethAuthentication ); + # get borrower information .... my ( $borr ) = GetMemberDetails( $borrowernumber ); --