Difference between revisions of "Shibboleth authentication"

From EPrints Documentation
Jump to: navigation, search
m
Line 2: Line 2:
 
'''TIMTOWTDI'''
 
'''TIMTOWTDI'''
 
(There Is More That One Way To Do It)
 
(There Is More That One Way To Do It)
 +
 +
====This is a work in progress====
 +
There are still errors in the fine detail, but the basic concept it valid
  
 
This is the system I implemented for using with the UK Access management Federation (which is a Shibboleth implimentation).
 
This is the system I implemented for using with the UK Access management Federation (which is a Shibboleth implimentation).
Line 74: Line 77:
 
    
 
    
 
   my $authentication_point = 'https://Your.Shibb.Authentication.Point/with/path';
 
   my $authentication_point = 'https://Your.Shibb.Authentication.Point/with/path';
   my $secret_code;
+
   my $code;
  my $orgName;
+
 
   my $puid;
 
   my $puid;
 
    
 
    
 
   if (! $session->have_parameters)
 
   if (! $session->have_parameters)
 
   {
 
   {
    my $args = $session->{'request'}->args();
+
    my $args = $session->{'request'}->args();
     $args =~ /s=(.*?)[&;\b]/;
+
     $args =~ /code=(.*?)[&;\b]/;
     $secret_code = $1; # $r->param('session');
+
     $secret_code = $1; # $r->param('secret_code');
 
   }
 
   }
 
   else {
 
   else {
     $secret_code = $session->param('s');
+
     $secret_code = $session->param('code');
 
     }
 
     }
 
   my $ath_returl = $session->get_full_url;
 
   my $ath_returl = $session->get_full_url;
 
    
 
    
  unless ($secret_code)
+
  unless ($secret_code) {
  {
+
    my $code = EPrints::Apache::AnApache::cookie( $session->get_request, "eprints_session" );
 +
    $ath_returl .= "&code=$code";  # append parameter to URL
 +
 
     redirect(
 
     redirect(
              url => '$authentication_point',
+
            url => '$authentication_point',
              params => {
+
            params => {
                context => $ath_returl,
+
              context => $ath_returl,
                service => 'myServiceCode',
+
              service => 'depot',
              }
+
                      }
            );
+
          );
 
     exit;  
 
     exit;  
 +
 
   }
 
   }
  
   ($puid, $orgName) = $session->{database}->get_auth_puid( $secret_code );
+
   $puid = $session->{database}->get_auth_puid( $secret_code );  
 
    
 
    
 
   return unless $puid;
 
   return unless $puid;
 
    
 
    
 
   my %return_hash = (
 
   my %return_hash = (
                      userID     => $puid,
+
          UserID     => $puid,
                      orgName    => $orgName,
+
                  );
                    );
+
 
+
 
   return \%return_hash;
 
   return \%return_hash;
 +
 
}
 
}
 
</pre>
 
</pre>
Line 134: Line 139:
 
my $orgid;
 
my $orgid;
 
my $uri;
 
my $uri;
 +
my $code;
  
 
my $session = new EPrints::Session;
 
my $session = new EPrints::Session;
 
exit( 0 ) unless( defined $session );
 
exit( 0 ) unless( defined $session );
  
my $code = EPrints::Apache::AnApache::cookie( $self->get_request, "eprints_session" );
+
$context    = $session->param('context');
 +
$context =~ /code=(\w+)$/;
 +
$code = $1; # $r->param('code');
  
 
if ( EPrints::Utils::is_set( $code ) )
 
if ( EPrints::Utils::is_set( $code ) )
 
{
 
{
 
+
   if ( $ENV{'REMOTE_ADDR'} =~ &&IP_NUMBER_OF_AUTHENTICATION_SERVER )
   $context    = $session->param('context');
+
   {
   $puid      = $session->param('id'  );
+
    $puid      = $session->param('id'  );
  $orgid      = $session->param('rorg');
+
    $orgid      = $session->param('rorg');
 
    
 
    
  my $sql = "REPLACE INTO auth_tickets VALUES( '".
+
    my $sql = "REPLACE INTO auth_tickets VALUES( '".
            EPrints::Database::prep_value($code). # the ticket-code
+
              EPrints::Database::prep_value($code). # the ticket-code
            "', '".
+
              "', '".
            EPrints::Database::prep_value($puid)  # puid
+
              EPrints::Database::prep_value($puid). # puid
            "', '".
+
              "', '".
            EPrints::Database::prep_value($orgid) # orgid
+
              EPrints::Database::prep_value($orgid). # orgid
            "', ".
+
              "', ".
            (time+60*5).                         # expire-time (5 minutes from now)
+
              (time+60*5).                           # expire-time (5 minutes from now)
            " )";
+
              " )";
  my $sth = $self->{database}->do( $sql );
+
    my $sth = $session->{'database'}->do( $sql );
 
+
 
  $uri = "$context&s=$code";
+
    $uri = "$context";
 +
  } else {
 +
    $uri = $session->get_repository->get_conf( "base_url" )."cgi/register";
 +
  }
 +
  print "$uri\n";
 +
  exit;
 +
 
 
} else {
 
} else {
   $uri = $session->get_repository->get_conf( "base_url" )."cgi/register"
+
  my %opts = ();
 +
  my @a = ();
 +
  for(1..16) { push @a, sprintf( "%02X",int rand 256 ); }
 +
   $opts{code} = join( "", @a );
 +
  $session->set_cookies( %opts );
 +
  $session->redirect( $session->get_repository->get_conf( "base_url" )."cgi/register" );
 +
  $session->terminate;
 +
  return Apache2::Const::DONE;
 
}
 
}
 
 
print "$uri\n";
 
print "$uri\n";
 +
 
exit;
 
exit;
 
</pre>
 
</pre>
As an extra precaution, it may be advisable to have an extra <code>$ENV{'REMOTE_ADDR'} =~ /111.222.333.444/ &&</code> in the '''if''' expression - to restrict access to just the Shibb server (there is no such thing as too much security in these things.)
 
  
 
==Using the code==
 
==Using the code==
Line 183: Line 203:
 
      
 
      
 
     $v->{puid} = $authorise->{'edinaUserID'};
 
     $v->{puid} = $authorise->{'edinaUserID'};
    $orgid = $authorise->{'organisationNum'};
 
 
</pre>
 
</pre>

Revision as of 07:44, 2 May 2008

Shibboleth Authentication

TIMTOWTDI (There Is More That One Way To Do It)

This is a work in progress

There are still errors in the fine detail, but the basic concept it valid

This is the system I implemented for using with the UK Access management Federation (which is a Shibboleth implimentation).

How Shibboleth Works

The UK-AMF authentication is reasonably complicated:

  • The user is directed to a Shibboleth authentication point, supplying a Context URL.
  • Once the user has been authenticated, the Context URL is called.
  • The Context URL processes the data from the authentication point and returns a URL with an initiated session (however (if!) the web site uses session management)
  • The user's browser is redirected to that URL (effectively by their own browser)

How to make EPrints do this

There are two problems when implimenting this under EPrints:

  • EPrints does it's session management in a cookie not via a URL parameter
    • It is desirable to leave as much of that session management as standard as possible.
  • The user Authentication (and Identification) and the actual Login process are done in different places, with multiple HTTP page calls.
    • Passing user identification details as parameters in an insecure http request means that someone can fake authenticated credentials and thus get access when they shouldn't.

What I have implemented is something similar to the standard eprints login_ticket system: the context URL phase sets some entries in an authorisation table, and then the main EPrints code just pulls the identity parameters from the table.

Adding a table to the database

In perl_lib/EPrints/Database.pm add the following code: In sub create_archive_tables, add

	$success = $success && $self->create_auth_tickets_table();

and then add the following two mathods

# $db->create_auth_tickets_table()
# 
# create the auth_tickets table.

sub create_auth_tickets_table
{
  my( $self ) = @_;

  my $sql = "CREATE TABLE auth_tickets ( code CHAR(32) NOT NULL, puid VARCHAR(255), orgid VARCHAR(64), expires INTEGER, primary key( code ) )";

  return $self->do( $sql );
}

# $db->get_auth_puid( $code )
# 
# return the puid, if any, associated with the given auth ticket code.

sub get_auth_puid
{
  my( $self, $code ) = @_;

  my $sql;

  # clean up old tickets
  $sql = "DELETE FROM auth_tickets WHERE ".time." > expires";
  $self->do( $sql );

  $sql = "SELECT puid,orgid FROM auth_tickets WHERE code='".prep_value($code)."'";
  my $sth = $self->prepare( $sql );
  $self->execute( $sth , $sql );
  my( $puid, $orgid ) = $sth->fetchrow_array;
  $sth->finish;

  return ($puid, $orgid);
}

The Authentication routine

I have all my authentication routines in the same place (perl_lib/EPrints/myCode/Autho.pm) which makes it available in multiple places.

The Shibboleth routine is:

sub shibb_autho {
   my ($session) = @_;
   
   my $authentication_point = 'https://Your.Shibb.Authentication.Point/with/path';
   my $code;
   my $puid;
   
   if (! $session->have_parameters)
   {
    my $args = $session->{'request'}->args();
     $args =~ /code=(.*?)[&;\b]/;
     $secret_code = $1; # $r->param('secret_code');
   }
   else {
     $secret_code = $session->param('code');
    }
   my $ath_returl = $session->get_full_url;
   
  unless ($secret_code) {
    my $code = EPrints::Apache::AnApache::cookie( $session->get_request, "eprints_session" );
    $ath_returl .= "&code=$code";  # append parameter to URL
 
    redirect(
            url => '$authentication_point',
            params => {
              context => $ath_returl,
              service => 'depot',
                      }
           );
    exit; 

  }

  $puid = $session->{database}->get_auth_puid( $secret_code ); 
  
  return unless $puid;
  
  my %return_hash = (
           UserID     => $puid,
                   );

  return \%return_hash;

}

Looking at the code, it breaks down into two main parts:

  • If there is no secret code then we assume the user has not been via the authentication point, and send them there
  • If we have a secret code then we use it as the index for the authentication table, and pull out the puid.
    • if we don't get a puid, return (as a failure)
    • if we have got a puid, build a return-hash and send it back.

The context URL

The script for reading the authentication details needs to be within the EPrints server, and ideally protected by a secure (https) connection.

The actual code is very simple: get the EPrints session code from the EPpints cookie, and use that as the index for the data supplied by the Shibboleth authentication server.

use EPrints;

use strict;

my $context;
my $puid;
my $orgid;
my $uri;
my $code;

my $session = new EPrints::Session;
exit( 0 ) unless( defined $session );

$context    = $session->param('context');
$context =~ /code=(\w+)$/;
$code = $1; # $r->param('code');

if ( EPrints::Utils::is_set( $code ) )
{
  if ( $ENV{'REMOTE_ADDR'} =~ &&IP_NUMBER_OF_AUTHENTICATION_SERVER )
  {
    $puid       = $session->param('id'  );
    $orgid      = $session->param('rorg');
  
    my $sql = "REPLACE INTO auth_tickets VALUES( '".
              EPrints::Database::prep_value($code).  # the ticket-code
              "', '".
              EPrints::Database::prep_value($puid).  # puid
              "', '".
              EPrints::Database::prep_value($orgid). # orgid
              "', ".
              (time+60*5).                           # expire-time (5 minutes from now)
              " )";
    my $sth = $session->{'database'}->do( $sql );

    $uri = "$context";
  } else {
    $uri = $session->get_repository->get_conf( "base_url" )."cgi/register";
  }
  print "$uri\n";
  exit;

} else {
  my %opts = ();
  my @a = ();
  for(1..16) { push @a, sprintf( "%02X",int rand 256 ); }
  $opts{code} = join( "", @a );
  $session->set_cookies( %opts );
  $session->redirect( $session->get_repository->get_conf( "base_url" )."cgi/register" );
  $session->terminate;
  return Apache2::Const::DONE;
}
print "$uri\n";

exit;

Using the code

Using the code is now pretty easy:

    $authorise = EPrints::myCode::Autho::shibb_autho($session);
    unless ( $authorise )
    {
        return mk_err_page( 
          $session,
          "cgi/register:no_shibb", 
          $fieldlist,
          $v,
        );
     }
     
     $v->{puid} = $authorise->{'edinaUserID'};