Difference between revisions of "Shibboleth authentication"
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 $ | + | my $code; |
− | |||
my $puid; | my $puid; | ||
if (! $session->have_parameters) | if (! $session->have_parameters) | ||
{ | { | ||
− | + | my $args = $session->{'request'}->args(); | |
− | $args =~ / | + | $args =~ /code=(.*?)[&;\b]/; |
− | $secret_code = $1; # $r->param(' | + | $secret_code = $1; # $r->param('secret_code'); |
} | } | ||
else { | else { | ||
− | $secret_code = $session->param(' | + | $secret_code = $session->param('code'); |
} | } | ||
my $ath_returl = $session->get_full_url; | 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( | redirect( | ||
− | + | url => '$authentication_point', | |
− | + | params => { | |
− | + | context => $ath_returl, | |
− | + | service => 'depot', | |
− | + | } | |
− | + | ); | |
exit; | exit; | ||
+ | |||
} | } | ||
− | + | $puid = $session->{database}->get_auth_puid( $secret_code ); | |
return unless $puid; | return unless $puid; | ||
my %return_hash = ( | my %return_hash = ( | ||
− | + | UserID => $puid, | |
− | + | ); | |
− | + | ||
− | |||
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 ); | ||
− | + | $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 ) | ||
+ | { | ||
+ | $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 { | } 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; | exit; | ||
</pre> | </pre> | ||
− | |||
==Using the code== | ==Using the code== | ||
Line 183: | Line 203: | ||
$v->{puid} = $authorise->{'edinaUserID'}; | $v->{puid} = $authorise->{'edinaUserID'}; | ||
− | |||
</pre> | </pre> |
Revision as of 07:44, 2 May 2008
Contents
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'};