Difference between revisions of "Shibboleth authentication"
| Line 119: | Line 119: | ||
** if we don't get a puid, return (as a failure) | ** 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. | ** 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 (<code>https</code>) 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. | ||
| + | |||
| + | <pre> | ||
| + | use EPrints; | ||
| + | |||
| + | use strict; | ||
| + | |||
| + | my $context; | ||
| + | my $puid; | ||
| + | my $orgid; | ||
| + | my $uri; | ||
| + | |||
| + | my $session = new EPrints::Session; | ||
| + | exit( 0 ) unless( defined $session ); | ||
| + | |||
| + | my $code = EPrints::Apache::AnApache::cookie( $self->get_request, "eprints_session" ); | ||
| + | |||
| + | if ( EPrints::Utils::is_set( $code ) ) | ||
| + | { | ||
| + | |||
| + | $context = $session->param('context'); | ||
| + | $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 = $self->{database}->do( $sql ); | ||
| + | |||
| + | $uri = "$context&s=$code"; | ||
| + | } else { | ||
| + | $uri = $session->get_repository->get_conf( "base_url" )."cgi/register" | ||
| + | } | ||
| + | |||
| + | print "$uri\n"; | ||
| + | exit; | ||
| + | </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=== | ||
Revision as of 11:04, 28 April 2008
Contents
Shibboleth Authentication
TIMTOWTDI (There Is More That One Way To Do It)
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 $secret_code;
my $orgName;
my $puid;
if (! $session->have_parameters)
{
my $args = $session->{'request'}->args();
$args =~ /s=(.*?)[&;\b]/;
$secret_code = $1; # $r->param('session');
}
else {
$secret_code = $session->param('s');
}
my $ath_returl = $session->get_full_url;
unless ($secret_code)
{
redirect(
url => '$authentication_point',
params => {
context => $ath_returl,
service => 'myServiceCode',
}
);
exit;
}
($puid, $orgName) = $session->{database}->get_auth_puid( $secret_code );
return unless $puid;
my %return_hash = (
userID => $puid,
orgName => $orgName,
);
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 $session = new EPrints::Session;
exit( 0 ) unless( defined $session );
my $code = EPrints::Apache::AnApache::cookie( $self->get_request, "eprints_session" );
if ( EPrints::Utils::is_set( $code ) )
{
$context = $session->param('context');
$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 = $self->{database}->do( $sql );
$uri = "$context&s=$code";
} else {
$uri = $session->get_repository->get_conf( "base_url" )."cgi/register"
}
print "$uri\n";
exit;
As an extra precaution, it may be advisable to have an extra $ENV{'REMOTE_ADDR'} =~ /111.222.333.444/ && 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 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'};
$orgid = $authorise->{'organisationNum'};