Difference between revisions of "CAS"
(→Create the database to store cookies: update schema and version) |
(→Eprints::Session edit: wrong function, wrong query, wrong package) |
||
(One intermediate revision by the same user not shown) | |||
Line 49: | Line 49: | ||
===Configure your secure host=== | ===Configure your secure host=== | ||
You must provide some information like the CAS host. You can provide it in your virtual host, or in AuthCAS.pm. | You must provide some information like the CAS host. You can provide it in your virtual host, or in AuthCAS.pm. | ||
− | Read the [http://search.cpan.org/~jhitt/Apache2-AuthCAS-0. | + | Read the [http://search.cpan.org/~jhitt/Apache2-AuthCAS-0.4/lib/Apache2/AuthCAS.pm module page on CPAN] to know more about it. |
Edit $EPRINTS_ROOT/archives/$ARCHIVE_ID/var/manual-secure.conf and add the lines: | Edit $EPRINTS_ROOT/archives/$ARCHIVE_ID/var/manual-secure.conf and add the lines: | ||
Line 74: | Line 74: | ||
CASRemoveTicket "true" | CASRemoveTicket "true" | ||
CASPretendBasicAuth "true" | CASPretendBasicAuth "true" | ||
+ | |||
+ | # CASLoginUri "/cas/login" | ||
+ | # CASLogoutUri "/cas/logout" | ||
PerlAuthzHandler EPrints::Apache::Auth::authz | PerlAuthzHandler EPrints::Apache::Auth::authz | ||
Line 85: | Line 88: | ||
</pre> | </pre> | ||
− | ==Eprints:: | + | ==Eprints::Repository edit== |
− | Now edit the $EPRINTS_PATH/perl_lib/EPrints/ | + | Now edit the $EPRINTS_PATH/perl_lib/EPrints/Repository.pm. |
− | + | (( previously, a modification to the CGI module declaration was suggested, it works without this modification on my instance )) | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | my | ||
− | |||
− | |||
− | Here is | + | Here is the complete current_user function with English/French comments |
<pre> | <pre> | ||
sub current_user | sub current_user | ||
{ | { | ||
− | + | my( $self ) = @_; | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if( $self->{offline} ) | |
− | + | { | |
− | + | return undef; | |
− | + | } | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if( $self->{logged_out} ) | |
− | + | { | |
− | + | return undef; | |
− | + | } | |
− | + | # info : $self->log loggue dans error.log d'Apache, même pas celui lié à l'instance ... | |
− | + | # info : $self->log logs in Apache's error.log not the one defined for eprints vhost ... | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | if( !defined $self->{current_user} ) { | |
− | + | # 1ère vérification, par cookie car il s'agit du fonctionnement standard et que même CAS fixe ce cookie. Il est important de ne pas passer dans le processus CAS/LDAP à chaque appel à la méthode | |
− | + | # 1st check, cookie check because it's eprints standard, even CAS sets this cookie. It's important not to go through the CAS/LDAP process at each method call | |
− | + | if ($self->get_archive->get_conf("cookie_auth")) { | |
− | + | $self->{current_user} = $self->_current_user_auth_cookie; | |
− | + | ||
− | + | # pas de cookie avec l'utilisateur, il faut passer via le process CAS s'il est activé | |
+ | # no user cookie, must go through the CAS process if defined | ||
+ | if (! defined($self->{current_user})) { | ||
+ | my $username = undef; | ||
+ | if ($self->get_archive->get_conf('cas_auth')) { | ||
+ | my $cookie_name = $self->get_archive->get_conf('cas_cookie_name'); | ||
+ | |||
+ | # la query (objet CGI) n'est pas initialisé lors des 1ers passages dans cette méthode | ||
+ | # the query (CGI object) is not set at the first calls of this method | ||
+ | if (defined($self->{query})) { | ||
+ | my $ticket = $self->{query}->cookie($cookie_name); | ||
+ | # récupération du nom d'utilisateur via la base de données mise à jour par le module CAS chargé par Apache | ||
+ | # get the username from database updated by the CAS module from Apache mod_perl | ||
+ | if ($ticket ne '') { | ||
+ | my $sql="SELECT user_id FROM cas_sessions WHERE id='$ticket'"; | ||
+ | my $sth = $self->get_database()->prepare($sql); | ||
+ | $self->get_database()->execute($sth,$ticket); | ||
+ | my @info = $sth->fetchrow_array(); | ||
+ | # my @list = split(":",$info[0]); | ||
+ | $username = $info[0]; | ||
+ | $sth->finish; | ||
+ | } | ||
+ | } | ||
+ | $self->{current_user} = EPrints::DataObj::User::user_with_username($self, $username); | ||
+ | } | ||
− | + | # la méthode au-dessus ne créé pas l'utilisateur si celui n'est pas déjà présent dans l'application, à faire via le LDAP | |
− | + | # the previous method doesn't create user if he doesn't exist in the database, we'll do this with an LDAP | |
− | + | if (defined($username) && ! defined($self->{current_user})) { | |
− | + | # récupération de tous les paramètres de connexion et recherche dans l'annuaire LDAP | |
− | + | # get every parameter to set the connection and search the user in the LDAP directory | |
− | + | my $ldap_host = $self->get_archive()->get_conf('ldap_host'); | |
+ | my $ldap_version = $self->get_archive()->get_conf('ldap_version'); | ||
+ | my $ldap_bind_user = $self->get_archive()->get_conf('ldap_bind_user'); | ||
+ | my $ldap_bind_pass = $self->get_archive()->get_conf('ldap_bind_pass'); | ||
+ | my $ldap_base = $self->get_archive()->get_conf('ldap_base'); | ||
+ | my $ldap_scope = $self->get_archive()->get_conf('ldap_scope'); | ||
+ | my $ldap_search_string = $self->get_archive()->get_conf('ldap_search_string'); | ||
+ | $ldap_search_string =~ s/!!USERNAME!!/$username/g; | ||
+ | |||
+ | # récupération des valeurs par défaut que le LDAP ne renverra pas (sauf email) | ||
+ | # get all default values, except email because we don't need it in our instance | ||
+ | my $ldap_default_email = $self->get_archive()->get_conf('ldap_default_email'); | ||
+ | my $ldap_default_country = $self->get_archive()->get_conf('ldap_default_country'); | ||
+ | my $ldap_default_org = $self->get_archive()->get_conf('ldap_default_org'); | ||
+ | my $ldap_conforms_supann = $self->get_archive()->get_conf('ldap_conforms_supann'); | ||
+ | |||
+ | # connexion à l'annuaire LDAP | ||
+ | # LDAP directory connection | ||
+ | my $ldap = Net::LDAP->new( $ldap_host ) or die "$@"; | ||
+ | my $mesg = $ldap->bind ( $ldap_bind_user, password => $ldap_bind_pass, version => $ldap_version); | ||
− | + | # recherche de l'utilisateur | |
− | + | # user search | |
− | + | my $result = $ldap->search(base => $ldap_base, scope => $ldap_scope, filter => $ldap_search_string); | |
− | |||
− | |||
− | |||
− | + | # les entrées sont censées être uniques, on prend la 1ère entrée | |
− | + | # user entry is meant to be unique, we take the first | |
− | + | my @entries = $result->entries(); | |
− | + | if (defined (my $ldap_entry = $entries[0])) { | |
− | + | # récupération du type d'utilisateur par défaut | |
− | + | # get the default user type | |
+ | my $usertype = $self->config("default_user_type"); | ||
+ | |||
+ | my $ds = $self->dataset("user"); | ||
+ | my $user = $ds->create_object( $self, { username => $username, usertype => $usertype } ); | ||
+ | $self->log(Dumper($user)); | ||
+ | # the previous version was using create_user, deprecated and non-working | ||
+ | # my $user = EPrints::User::create_user($self, $usertype); | ||
− | + | my $name = {}; | |
− | + | $name->{family} = $ldap_entry->get_value("sn"); | |
− | + | $name->{given} = $ldap_entry->get_value("givenName"); | |
− | + | $user->set_value("name", $name); | |
− | |||
− | |||
− | + | # Set Email | |
− | + | my $email = defined $ldap_entry->get_value("mail") ? $ldap_entry->get_value("mail") : $ldap_default_email; | |
− | + | $user->set_value("email", $email); | |
− | |||
− | + | if ($ldap_conforms_supann) { | |
− | + | $user->set_value("org", $ldap_entry->get_value("supannEtablissement")); | |
− | + | } | |
− | + | else { | |
− | + | $user->set_value("org", $ldap_default_org); | |
− | + | } | |
− | |||
− | |||
− | + | # Set Address | |
− | + | # my $address = $ldap_entry->get_value("postalAddress") | |
− | + | # . "\n" | |
− | + | # . $ldap_entry->get_value("postalCode") | |
− | + | # . " " | |
− | + | # . $ldap_entry->get_value("l"); | |
− | + | # $user->set_value("address", $address); | |
− | + | $user->set_value("country", $ldap_default_country); | |
− | + | # Set URL | |
− | + | # $user->set_value("url", $ldap_entry->get_value("labeledURI")); | |
− | + | ||
+ | # save the information into the database and gives the user an id | ||
+ | $user->commit(); | ||
− | + | $self->{current_user} = $user; | |
− | + | } | |
− | + | } | |
− | + | # la fonction est aussi appelée avant tout procédé d'authentification, même si l'on accède à la page de login | |
− | + | # en fait, elle est appelée très souvent, d'où le test qui suit | |
− | + | # the method is also called before any authentication process, need to check again at this point | |
− | + | if (defined($self->{current_user})) { | |
− | + | # création d'un ticket de login interne à l'appli et connexion de l'utilisateur (méthode trouvée dans le package Register.pm où un utilisateur est connecté après avoir été créé) | |
− | + | # Create a login ticket and log the user in | |
− | + | EPrints::DataObj::LoginTicket->expire_all( $self ); | |
− | + | $self->dataset( "loginticket" )->create_dataobj({ | |
− | + | userid => $self->{current_user}->id, | |
− | + | })->set_cookies(); | |
− | + | } | |
+ | } | ||
+ | } | ||
+ | else { | ||
+ | # custom auth | ||
+ | if( $self->get_repository->can_call( 'get_current_user' ) ) | ||
+ | { | ||
+ | $self->{current_user} = $self->get_repository->call( 'get_current_user', $self ); | ||
+ | } | ||
+ | # basic auth | ||
+ | if( !defined $self->{current_user} ) | ||
+ | { | ||
+ | $self->{current_user} = $self->_current_user_auth_basic; | ||
+ | } | ||
+ | } | ||
+ | $self->{already_in_current_user} = 0; | ||
+ | } | ||
+ | $self->{processor}->{user} = $self->{current_user}; | ||
+ | return $self->{current_user}; | ||
} | } | ||
</pre> | </pre> | ||
+ | In this method, I have commented some stuff around addresses because I had no need. By the way, you can use/add every useful information from your LDAP that has a place to go in the user object. | ||
− | This code assumes | + | This code assumes Apache2::AuthCAS SQL tables are stored with the rest of the Eprints server. It won't work if you use a Postgres database or even if you stored your Apache2::AuthCAS tables on an other MySQL database. If you have any good reason to use a separated database, you will have to change some lines in order to make it work. |
==Configure your archive== | ==Configure your archive== |
Latest revision as of 14:01, 30 October 2015
This page explains how to use a CAS server to authenticate user in eprints.
Contents
Install a secure host
The first thing you'll have to do is to install a secure host.
Apache2::AuthCAS
This perl library allows you to easily communicate with a CAS sever.
Install the lib
This can be done with the command: perl -MCPAN -e 'install Apache2::AuthCAS'
More infomartion are available on http://search.cpan.org/~jhitt/Apache2-AuthCAS-0.4/lib/Apache2/AuthCAS.pm
Create the database to store cookies
You should find this schema on http://search.cpan.org/src/JHITT/Apache2-AuthCAS-0.4/schemaPg.sql
-- Schema for use with PostgreSQL CREATE TABLE cas_sessions ( id varchar(32) not null primary key , last_accessed int8 not null , user_id varchar(32) not null , pgtiou varchar(256) , pgt varchar(256) , service_ticket varchar(256) ); CREATE INDEX cas_sessions_id_index ON cas_sessions(id); CREATE INDEX cas_sessions_pgtiou_index ON cas_sessions(pgtiou); -- Schema for use with MySQL CREATE TABLE cas_sessions ( id varchar(32) not null primary key , last_accessed int(8) not null , user_id varchar(32) not null , pgtiou varchar(256) , pgt varchar(256) , service_ticket varchar(256) ); -- MySQL will create automatically the index for the primary key CREATE INDEX cas_sessions_pgtiou_index ON cas_sessions(pgtiou);
Configure your secure host
You must provide some information like the CAS host. You can provide it in your virtual host, or in AuthCAS.pm. Read the module page on CPAN to know more about it.
Edit $EPRINTS_ROOT/archives/$ARCHIVE_ID/var/manual-secure.conf and add the lines:
PerlLoadModule Apache2::AuthCAS::Configuration PerlLoadModule Apache2::AuthCAS <Directory "/opt/eprints3/cgi/users"> AuthName "User Area" AuthType Apache2::AuthCAS AuthName "CAS" PerlAuthenHandler Apache2::AuthCAS->authenticate CASHost "HOST" CASPort "443" CASErrorURL "https://HOST/cas/error/" CASDbDataSource "dbname=DATABASE_NAME" CASDbDriver "mysql" CASDbUser "DATABASE_USERNAME" CASDbPass "DATABASE_PASSWORD" CASSessionCookieName "COOKIE_NAME" CASSessionTimeout "1800" CASLogLevel "0" CASRemoveTicket "true" CASPretendBasicAuth "true" # CASLoginUri "/cas/login" # CASLogoutUri "/cas/logout" PerlAuthzHandler EPrints::Apache::Auth::authz Require valid-user SetHandler perl-script PerlHandler ModPerl::Registry PerlSendHeader Off Options ExecCGI FollowSymLinks </Directory>
Eprints::Repository edit
Now edit the $EPRINTS_PATH/perl_lib/EPrints/Repository.pm. (( previously, a modification to the CGI module declaration was suggested, it works without this modification on my instance ))
Here is the complete current_user function with English/French comments
sub current_user { my( $self ) = @_; if( $self->{offline} ) { return undef; } if( $self->{logged_out} ) { return undef; } # info : $self->log loggue dans error.log d'Apache, même pas celui lié à l'instance ... # info : $self->log logs in Apache's error.log not the one defined for eprints vhost ... if( !defined $self->{current_user} ) { # 1ère vérification, par cookie car il s'agit du fonctionnement standard et que même CAS fixe ce cookie. Il est important de ne pas passer dans le processus CAS/LDAP à chaque appel à la méthode # 1st check, cookie check because it's eprints standard, even CAS sets this cookie. It's important not to go through the CAS/LDAP process at each method call if ($self->get_archive->get_conf("cookie_auth")) { $self->{current_user} = $self->_current_user_auth_cookie; # pas de cookie avec l'utilisateur, il faut passer via le process CAS s'il est activé # no user cookie, must go through the CAS process if defined if (! defined($self->{current_user})) { my $username = undef; if ($self->get_archive->get_conf('cas_auth')) { my $cookie_name = $self->get_archive->get_conf('cas_cookie_name'); # la query (objet CGI) n'est pas initialisé lors des 1ers passages dans cette méthode # the query (CGI object) is not set at the first calls of this method if (defined($self->{query})) { my $ticket = $self->{query}->cookie($cookie_name); # récupération du nom d'utilisateur via la base de données mise à jour par le module CAS chargé par Apache # get the username from database updated by the CAS module from Apache mod_perl if ($ticket ne '') { my $sql="SELECT user_id FROM cas_sessions WHERE id='$ticket'"; my $sth = $self->get_database()->prepare($sql); $self->get_database()->execute($sth,$ticket); my @info = $sth->fetchrow_array(); # my @list = split(":",$info[0]); $username = $info[0]; $sth->finish; } } $self->{current_user} = EPrints::DataObj::User::user_with_username($self, $username); } # la méthode au-dessus ne créé pas l'utilisateur si celui n'est pas déjà présent dans l'application, à faire via le LDAP # the previous method doesn't create user if he doesn't exist in the database, we'll do this with an LDAP if (defined($username) && ! defined($self->{current_user})) { # récupération de tous les paramètres de connexion et recherche dans l'annuaire LDAP # get every parameter to set the connection and search the user in the LDAP directory my $ldap_host = $self->get_archive()->get_conf('ldap_host'); my $ldap_version = $self->get_archive()->get_conf('ldap_version'); my $ldap_bind_user = $self->get_archive()->get_conf('ldap_bind_user'); my $ldap_bind_pass = $self->get_archive()->get_conf('ldap_bind_pass'); my $ldap_base = $self->get_archive()->get_conf('ldap_base'); my $ldap_scope = $self->get_archive()->get_conf('ldap_scope'); my $ldap_search_string = $self->get_archive()->get_conf('ldap_search_string'); $ldap_search_string =~ s/!!USERNAME!!/$username/g; # récupération des valeurs par défaut que le LDAP ne renverra pas (sauf email) # get all default values, except email because we don't need it in our instance my $ldap_default_email = $self->get_archive()->get_conf('ldap_default_email'); my $ldap_default_country = $self->get_archive()->get_conf('ldap_default_country'); my $ldap_default_org = $self->get_archive()->get_conf('ldap_default_org'); my $ldap_conforms_supann = $self->get_archive()->get_conf('ldap_conforms_supann'); # connexion à l'annuaire LDAP # LDAP directory connection my $ldap = Net::LDAP->new( $ldap_host ) or die "$@"; my $mesg = $ldap->bind ( $ldap_bind_user, password => $ldap_bind_pass, version => $ldap_version); # recherche de l'utilisateur # user search my $result = $ldap->search(base => $ldap_base, scope => $ldap_scope, filter => $ldap_search_string); # les entrées sont censées être uniques, on prend la 1ère entrée # user entry is meant to be unique, we take the first my @entries = $result->entries(); if (defined (my $ldap_entry = $entries[0])) { # récupération du type d'utilisateur par défaut # get the default user type my $usertype = $self->config("default_user_type"); my $ds = $self->dataset("user"); my $user = $ds->create_object( $self, { username => $username, usertype => $usertype } ); $self->log(Dumper($user)); # the previous version was using create_user, deprecated and non-working # my $user = EPrints::User::create_user($self, $usertype); my $name = {}; $name->{family} = $ldap_entry->get_value("sn"); $name->{given} = $ldap_entry->get_value("givenName"); $user->set_value("name", $name); # Set Email my $email = defined $ldap_entry->get_value("mail") ? $ldap_entry->get_value("mail") : $ldap_default_email; $user->set_value("email", $email); if ($ldap_conforms_supann) { $user->set_value("org", $ldap_entry->get_value("supannEtablissement")); } else { $user->set_value("org", $ldap_default_org); } # Set Address # my $address = $ldap_entry->get_value("postalAddress") # . "\n" # . $ldap_entry->get_value("postalCode") # . " " # . $ldap_entry->get_value("l"); # $user->set_value("address", $address); $user->set_value("country", $ldap_default_country); # Set URL # $user->set_value("url", $ldap_entry->get_value("labeledURI")); # save the information into the database and gives the user an id $user->commit(); $self->{current_user} = $user; } } # la fonction est aussi appelée avant tout procédé d'authentification, même si l'on accède à la page de login # en fait, elle est appelée très souvent, d'où le test qui suit # the method is also called before any authentication process, need to check again at this point if (defined($self->{current_user})) { # création d'un ticket de login interne à l'appli et connexion de l'utilisateur (méthode trouvée dans le package Register.pm où un utilisateur est connecté après avoir été créé) # Create a login ticket and log the user in EPrints::DataObj::LoginTicket->expire_all( $self ); $self->dataset( "loginticket" )->create_dataobj({ userid => $self->{current_user}->id, })->set_cookies(); } } } else { # custom auth if( $self->get_repository->can_call( 'get_current_user' ) ) { $self->{current_user} = $self->get_repository->call( 'get_current_user', $self ); } # basic auth if( !defined $self->{current_user} ) { $self->{current_user} = $self->_current_user_auth_basic; } } $self->{already_in_current_user} = 0; } $self->{processor}->{user} = $self->{current_user}; return $self->{current_user}; }
In this method, I have commented some stuff around addresses because I had no need. By the way, you can use/add every useful information from your LDAP that has a place to go in the user object.
This code assumes Apache2::AuthCAS SQL tables are stored with the rest of the Eprints server. It won't work if you use a Postgres database or even if you stored your Apache2::AuthCAS tables on an other MySQL database. If you have any good reason to use a separated database, you will have to change some lines in order to make it work.
Configure your archive
Add the following lines into cfg/cfg.d/misc.pl:
$c->{cas_auth} = 1; $c->{cas_cookie_name} = 'COOKIE_NAME'; # The same as in manual-secure.conf $c->{ldap_host} = 'LDAP_HOST_NAME'; $c->{ldap_version} = 3; $c->{ldap_bind_user} = 'ou=admin,dc=DOMAIN,dc=com'; $c->{ldap_bind_pass} = 'SECRET_PASS'; $c->{ldap_base} = 'dc=DOMAIN,dc=com'; $c->{ldap_scope} = 'sub'; $c->{ldap_search_string} = '(uid=!!USERNAME!!)'; $c->{ldap_default_email} = 'A VALID EMAIL'; $c->{ldap_default_country} = 'USA'; $c->{ldap_default_org} = 'YOUR ORGANISATION';
Test your archive
You should now be able to login through your CAS server.
LDAP Configuration
Once CAS is working, you may need to copy the user entry from an LDAP server. EPrints3 comes with example code in archives/ARCHIVEID/cfg/cfg.d/user_login.pl. Just use it !