BUG8446: Adds Shibboleth authentication
authorMatthias Meusburger <matthias.meusburger@biblibre.com>
Wed, 15 Feb 2012 13:57:02 +0000 (14:57 +0100)
committerTomas Cohen Arazi <tomascohen@gmail.com>
Thu, 16 Oct 2014 15:27:42 +0000 (12:27 -0300)
 - 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

BUG8446, Follow-up: Adds Shibboleth authentication

 - 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)

BUG8446, Follow-up: Adds Shibboleth authentication

 - Adds redirect to opac after logout

BUG8446, Follow-up: Adds Shibboleth authentication

 - Shibboleth is not compatible with basic http authentication
   in C4/Auth.pm. This patch fixes that.

BUG8446, Follow-up: Adds Shibboleth authentication

 - Use ENV{'SERVER_NAME'} instead of syspref OpacBaseURL in order to work with
   multiple vhosts.

BUG8446, Follow-up: Adds Shibboleth authentication

 - Adds missing protocol for $ENV{'SERVER_NAME'}

Signed-off-by: Martin Renvoize <martin.renvoize@ptfs-europe.com>
Signed-off-by: Jesse Weaver <pianohacker@gmail.com>
Signed-off-by: Katrin Fischer <Katrin.Fischer.83@web.de>
Tested with the feide idp.
- LDAP login and logout are working
- local login/logout are still working
- CAS login/logout are still working

Instructions for setup can be found on the wiki:
http://wiki.koha-community.org/wiki/Shibboleth_Configuration

Signed-off-by: Tomas Cohen Arazi <tomascohen@gmail.com>

C4/Auth.pm
C4/Auth_with_Shibboleth.pm [new file with mode: 0644]
installer/data/mysql/updatedatabase.pl
koha-tmpl/intranet-tmpl/prog/en/modules/admin/preferences/admin.pref
koha-tmpl/opac-tmpl/prog/en/modules/opac-auth.tt
koha-tmpl/opac-tmpl/prog/en/modules/opac-main.tt
opac/opac-main.pl
opac/opac-user.pl

index 2b8a036..b2dd941 100644 (file)
@@ -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);
     }
@@ -669,8 +676,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    => '',
@@ -728,9 +745,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
@@ -800,13 +823,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 );
@@ -1053,6 +1083,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"),
@@ -1123,6 +1154,12 @@ sub checkauth {
         );
     }
 
+    if ($shib) {
+            $template->param(
+                shibbolethLoginUrl    => login_shib_url($query),
+            );
+    }
+
     my $self_url = $query->url( -absolute => 1 );
     $template->param(
         url         => $self_url,
@@ -1557,6 +1594,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(@_)
 }
 
diff --git a/C4/Auth_with_Shibboleth.pm b/C4/Auth_with_Shibboleth.pm
new file mode 100644 (file)
index 0000000..3b0a9fb
--- /dev/null
@@ -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;
index d6b7d24..cdad32e 100755 (executable)
@@ -8801,6 +8801,14 @@ if ( CheckVersion($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)
index c16138c..32665b2 100644 (file)
@@ -105,3 +105,22 @@ 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
+              default: Zebra
+              choices:
+                Solr: Solr
+                Zebra: Zebra
+            - is the search engine used.
index c06e234..9bf09c5 100644 (file)
 <p>You entered an incorrect username or password. Please try again! And remember, usernames and passwords are case sensitive.</p>
 [% END %]
 
+[% IF ( shibbolethAuthentication ) %]
+<h4>Shibboleth Login</h4>
+
+[% IF ( invalidShibLogin ) %]
+<!-- This is what is displated if shibboleth login has failed -->
+<p>Sorry, the shibboleth login failed.</p>
+[% END %]
+
+<p>If you have a shibboleth account,
+please <a href="[% shibbolethLoginUrl %]">click here to login</a>.</p>
+
+<h4>Local Login</h4>
+<p>If you do not have a shibboleth account, but a local account, you can still log in : </p>
+
+[% END %]
+
 [% IF ( casAuthentication ) %]
 <h4>Cas login</h4>
 
index a1be710..4a783e5 100644 (file)
@@ -47,6 +47,7 @@
     [% IF ( opacuserlogin ) %]
     [% UNLESS ( loggedinusername ) %]
     [% UNLESS ( casAuthentication ) %]
+    [% UNLESS ( shibbolethAuthentication ) %]
     <div id="login" class="container clearfix">
        <form action="/cgi-bin/koha/opac-user.pl" method="post" name="auth" id="auth">
     <input type="hidden" name="koha_login_context" value="opac" />
index 355c892..6eb6add 100755 (executable)
@@ -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
index 9f88c1a..9c7f292 100755 (executable)
@@ -74,6 +74,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 );