Restore CAS activation global rule (#1625)

This commit is contained in:
Christophe Maudoux 2019-02-06 22:16:34 +01:00
parent fc316991eb
commit 007a5432f9
2 changed files with 84 additions and 63 deletions

View File

@ -5,26 +5,38 @@ use Mouse;
use URI; use URI;
use Lemonldap::NG::Common::FormEncode; use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Portal::Main::Constants qw( use Lemonldap::NG::Portal::Main::Constants qw(
PE_CAS_SERVICE_NOT_ALLOWED PE_CAS_SERVICE_NOT_ALLOWED
PE_CONFIRM PE_CONFIRM
PE_ERROR PE_ERROR
PE_LOGOUT_OK PE_LOGOUT_OK
PE_OK PE_OK
PE_SENDRESPONSE PE_SENDRESPONSE
); );
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Issuer', extends 'Lemonldap::NG::Portal::Main::Issuer',
'Lemonldap::NG::Portal::Lib::CAS'; 'Lemonldap::NG::Portal::Lib::CAS';
# INITIALIZATION # INITIALIZATION
use constant beforeAuth => 'storeEnvAndCheckGateway'; use constant beforeAuth => 'storeEnvAndCheckGateway';
use constant sessionKind => 'ICAS'; use constant sessionKind => 'ICAS';
has rule => ( is => 'rw', default => sub { {} } );
sub init { sub init {
my ($self) = @_; my ($self) = @_;
my $hd = $self->p->HANDLER;
$self->logger->debug( "CAS Rule -> " . $self->conf->{issuerDBCASRule} );
my $rule
= $hd->buildSub( $hd->substitute( $self->conf->{issuerDBCASRule} ) );
unless ($rule) {
$self->error( "Bad CAS rule " . $hd->tsv->{jail}->error );
return 0;
}
$self->{rule} = $rule;
# Launch parents initialization subroutines, then launch IdP en SP lists # Launch parents initialization subroutines, then launch IdP en SP lists
my $res = $self->Lemonldap::NG::Portal::Main::Issuer::init(); my $res = $self->Lemonldap::NG::Portal::Main::Issuer::init();
@ -50,9 +62,9 @@ sub init {
sub storeEnvAndCheckGateway { sub storeEnvAndCheckGateway {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
my $service = $self->p->getHiddenFormValue( $req, 'service' ) my $service = $self->p->getHiddenFormValue( $req, 'service' )
|| $req->param('service'); || $req->param('service');
my $gateway = $self->p->getHiddenFormValue( $req, 'gateway' ) my $gateway = $self->p->getHiddenFormValue( $req, 'gateway' )
|| $req->param('gateway'); || $req->param('gateway');
if ( $gateway and $gateway eq "true" ) { if ( $gateway and $gateway eq "true" ) {
$self->logger->debug( $self->logger->debug(
@ -78,6 +90,11 @@ sub storeEnvAndCheckGateway {
sub run { sub run {
my ( $self, $req, $target ) = @_; my ( $self, $req, $target ) = @_;
unless ( $self->rule->( $req, $req->sessionInfo ) ) {
$self->userLogger->error('CAS service not authorized');
return PE_CAS_SERVICE_NOT_ALLOWED;
}
# CAS URL # CAS URL
my $cas_login = 'login'; my $cas_login = 'login';
my $cas_logout = 'logout'; my $cas_logout = 'logout';
@ -104,18 +121,18 @@ sub run {
# GET parameters # GET parameters
my $service = $self->p->getHiddenFormValue( $req, 'service' ) my $service = $self->p->getHiddenFormValue( $req, 'service' )
|| $req->param('service'); || $req->param('service');
my $renew = my $renew = $self->p->getHiddenFormValue( $req, 'renew' )
$self->p->getHiddenFormValue( $req, 'renew' ) || $req->param('renew'); || $req->param('renew');
my $gateway = $self->p->getHiddenFormValue( $req, 'gateway' ) my $gateway = $self->p->getHiddenFormValue( $req, 'gateway' )
|| $req->param('gateway'); || $req->param('gateway');
my $casServiceTicket; my $casServiceTicket;
# Renew # Renew
if ( $renew if ( $renew
and $renew eq 'true' and $renew eq 'true'
and time - $req->sessionInfo->{_utime} > and time - $req->sessionInfo->{_utime}
$self->conf->{portalForceAuthnInterval} ) > $self->conf->{portalForceAuthnInterval} )
{ {
# Authentication must be replayed # Authentication must be replayed
@ -151,7 +168,8 @@ sub run {
} }
if ( my $rule = $self->spRules->{$app} ) { if ( my $rule = $self->spRules->{$app} ) {
if ( $rule->( $req, $req->sessionInfo ) ) { if ( $rule->( $req, $req->sessionInfo ) ) {
$self->logger->debug("CAS service $service access allowed"); $self->logger->debug(
"CAS service $service access allowed");
} }
else { else {
@ -160,14 +178,15 @@ sub run {
if ( $casAccessControlPolicy =~ /^(error)$/i ) { if ( $casAccessControlPolicy =~ /^(error)$/i ) {
$self->logger->debug( $self->logger->debug(
"Return error instead of redirecting user on CAS service" "Return error instead of redirecting user on CAS service"
); );
return PE_CAS_SERVICE_NOT_ALLOWED; return PE_CAS_SERVICE_NOT_ALLOWED;
} }
else { else {
$self->logger->debug( $self->logger->debug(
"Redirect user on CAS service with a fake ticket"); "Redirect user on CAS service with a fake ticket"
);
$casServiceTicket = "ST-F4K3T1CK3T"; $casServiceTicket = "ST-F4K3T1CK3T";
} }
} }
@ -179,10 +198,10 @@ sub run {
# Check last authentication time to decide if # Check last authentication time to decide if
# the authentication is recent or not # the authentication is recent or not
my $casRenewFlag = 0; my $casRenewFlag = 0;
my $last_authn_utime = $req->{sessionInfo}->{_lastAuthnUTime} || 0; my $last_authn_utime
if ( = $req->{sessionInfo}->{_lastAuthnUTime} || 0;
time() - $last_authn_utime < if (time() - $last_authn_utime
$self->conf->{portalForceAuthnInterval} ) < $self->conf->{portalForceAuthnInterval} )
{ {
$self->logger->debug( $self->logger->debug(
"Authentication is recent, will set CAS renew flag to true" "Authentication is recent, will set CAS renew flag to true"
@ -219,7 +238,7 @@ sub run {
# Redirect to service # Redirect to service
my $service_url = $service; my $service_url = $service;
$service_url .= ( $service =~ /\?/ ? '&' : '?' ) $service_url .= ( $service =~ /\?/ ? '&' : '?' )
. build_urlencoded( ticket => $casServiceTicket ); . build_urlencoded( ticket => $casServiceTicket );
$self->logger->debug("Redirect user to $service_url"); $self->logger->debug("Redirect user to $service_url");
@ -296,7 +315,8 @@ sub run {
} }
# 4. SERVICE VALIDATE [CAS 2.0] # 4. SERVICE VALIDATE [CAS 2.0]
if ( $target eq $cas_serviceValidate || $target eq $cas_p3_serviceValidate ) if ( $target eq $cas_serviceValidate
|| $target eq $cas_p3_serviceValidate )
{ {
$self->logger->debug( $self->logger->debug(
@ -304,7 +324,8 @@ sub run {
# This URL must not be called by authenticated users # This URL must not be called by authenticated users
$self->userLogger->info( $self->userLogger->info(
"CAS SERVICE VALIDATE URL called by authenticated user, ignore it"); "CAS SERVICE VALIDATE URL called by authenticated user, ignore it"
);
return PE_OK; return PE_OK;
} }
@ -312,7 +333,8 @@ sub run {
# 5. PROXY VALIDATE [CAS 2.0] # 5. PROXY VALIDATE [CAS 2.0]
if ( $target eq $cas_proxyValidate || $target eq $cas_p3_proxyValidate ) { if ( $target eq $cas_proxyValidate || $target eq $cas_p3_proxyValidate ) {
$self->logger->debug("URL $url detected as an CAS PROXY VALIDATE URL"); $self->logger->debug(
"URL $url detected as an CAS PROXY VALIDATE URL");
# This URL must not be called by authenticated users # This URL must not be called by authenticated users
$self->userLogger->info( $self->userLogger->info(
@ -394,20 +416,21 @@ sub validate {
or $service2_uri->rel($service1_uri) eq "./" ) or $service2_uri->rel($service1_uri) eq "./" )
{ {
$self->logger->notice( $self->logger->notice(
"Submitted service $service1_uri does not exactly match initial service " "Submitted service $service1_uri does not exactly match initial service "
. $service2_uri . $service2_uri
. ' but difference is tolerated.' ); . ' but difference is tolerated.' );
} }
else { else {
$self->logger->error( $self->logger->error(
"Submitted service $service does not match initial service " "Submitted service $service does not match initial service "
. $casServiceSession->data->{service} ); . $casServiceSession->data->{service} );
$self->deleteCasSession($casServiceSession); $self->deleteCasSession($casServiceSession);
return $self->returnCasValidateError(); return $self->returnCasValidateError();
} }
} }
else { else {
$self->logger->debug("Submitted service $service math initial servce"); $self->logger->debug(
"Submitted service $service math initial servce");
} }
# Check renew # Check renew
@ -418,7 +441,7 @@ sub validate {
unless ( $casServiceSession->data->{renew} ) { unless ( $casServiceSession->data->{renew} ) {
$self->logger->error( $self->logger->error(
"Authentication renew requested, but not done in former authentication process" "Authentication renew requested, but not done in former authentication process"
); );
$self->deleteCasSession($casServiceSession); $self->deleteCasSession($casServiceSession);
return $self->returnCasValidateError(); return $self->returnCasValidateError();
@ -426,21 +449,20 @@ sub validate {
} }
# Open local session # Open local session
my $localSession = my $localSession
$self->p->getApacheSession( $casServiceSession->data->{_cas_id} ); = $self->p->getApacheSession( $casServiceSession->data->{_cas_id} );
unless ($localSession) { unless ($localSession) {
$self->logger->warn( "Local session " $self->logger->warn( "Local session "
. $casServiceSession->data->{_cas_id} . $casServiceSession->data->{_cas_id}
. " notfound" ); . " notfound" );
$self->deleteCasSession($casServiceSession); $self->deleteCasSession($casServiceSession);
return $self->returnCasValidateError(); return $self->returnCasValidateError();
} }
# Get username # Get username
my $username = my $username = $localSession->data->{ $self->conf->{casAttr}
$localSession->data->{ $self->conf->{casAttr} || $self->conf->{whatToTrace} };
|| $self->conf->{whatToTrace} };
$self->logger->debug("Get username $username"); $self->logger->debug("Get username $username");
@ -549,8 +571,8 @@ sub _validate2 {
} }
$self->logger->debug( "Get " $self->logger->debug( "Get "
. lc($urlType) . lc($urlType)
. " validate request with ticket $ticket for service $service" ); . " validate request with ticket $ticket for service $service" );
# Get CAS session corresponding to ticket # Get CAS session corresponding to ticket
if ( $urlType eq 'SERVICE' and !( $ticket =~ s/^ST-// ) ) { if ( $urlType eq 'SERVICE' and !( $ticket =~ s/^ST-// ) ) {
@ -587,14 +609,14 @@ sub _validate2 {
or $service2_uri->rel($service1_uri) eq "./" ) or $service2_uri->rel($service1_uri) eq "./" )
{ {
$self->logger->notice( $self->logger->notice(
"Submitted service $service1_uri does not exactly match initial service " "Submitted service $service1_uri does not exactly match initial service "
. $service2_uri . $service2_uri
. ' but difference is tolerated.' ); . ' but difference is tolerated.' );
} }
else { else {
$self->userLogger->error( $self->userLogger->error(
"Submitted service $service does not match initial service " "Submitted service $service does not match initial service "
. $casServiceSession->data->{service} ); . $casServiceSession->data->{service} );
$self->deleteCasSession($casServiceSession); $self->deleteCasSession($casServiceSession);
return $self->returnCasServiceValidateError( $req, return $self->returnCasServiceValidateError( $req,
'INVALID_SERVICE', 'INVALID_SERVICE',
@ -614,7 +636,7 @@ sub _validate2 {
unless ( $casServiceSession->data->{renew} ) { unless ( $casServiceSession->data->{renew} ) {
$self->logger->error( $self->logger->error(
"Authentication renew requested, but not done in former authentication process" "Authentication renew requested, but not done in former authentication process"
); );
$self->deleteCasSession($casServiceSession); $self->deleteCasSession($casServiceSession);
return $self->returnCasValidateError(); return $self->returnCasValidateError();
@ -653,7 +675,7 @@ sub _validate2 {
if ($casProxyGrantingSession) { if ($casProxyGrantingSession) {
my $casProxyGrantingSessionID = $casProxyGrantingSession->id; my $casProxyGrantingSessionID = $casProxyGrantingSession->id;
my $casProxyGrantingTicket = "PGT-" . $casProxyGrantingSessionID; my $casProxyGrantingTicket = "PGT-" . $casProxyGrantingSessionID;
$self->logger->debug( $self->logger->debug(
"CAS proxy granting session $casProxyGrantingSessionID created" "CAS proxy granting session $casProxyGrantingSessionID created"
@ -667,16 +689,15 @@ sub _validate2 {
$casProxyGrantingTicketIOU = "PGTIOU-" . $tmpCasSession->id; $casProxyGrantingTicketIOU = "PGTIOU-" . $tmpCasSession->id;
$self->deleteCasSession($tmpCasSession); $self->deleteCasSession($tmpCasSession);
$self->logger->debug( $self->logger->debug(
"Generate proxy granting ticket IOU $casProxyGrantingTicketIOU" "Generate proxy granting ticket IOU $casProxyGrantingTicketIOU"
); );
# Request pgtUrl # Request pgtUrl
if ( if ($self->callPgtUrl(
$self->callPgtUrl(
$pgtUrl, $casProxyGrantingTicketIOU, $pgtUrl, $casProxyGrantingTicketIOU,
$casProxyGrantingTicket $casProxyGrantingTicket
) )
) )
{ {
$self->logger->debug( $self->logger->debug(
"Proxy granting URL $pgtUrl called with success"); "Proxy granting URL $pgtUrl called with success");
@ -696,31 +717,30 @@ sub _validate2 {
} }
# Open local session # Open local session
my $localSession = my $localSession
$self->p->getApacheSession( $casServiceSession->data->{_cas_id} ); = $self->p->getApacheSession( $casServiceSession->data->{_cas_id} );
unless ($localSession) { unless ($localSession) {
$self->userLogger->error( "Local session " $self->userLogger->error( "Local session "
. $casServiceSession->data->{_cas_id} . $casServiceSession->data->{_cas_id}
. " notfound" ); . " notfound" );
$self->deleteCasSession($casServiceSession); $self->deleteCasSession($casServiceSession);
return $self->returnCasServiceValidateError( $req, 'INTERNAL_ERROR', return $self->returnCasServiceValidateError( $req, 'INTERNAL_ERROR',
'No session associated to ticket' ); 'No session associated to ticket' );
} }
# Get username # Get username
my $username = my $username = $localSession->data->{ $self->conf->{casAttr}
$localSession->data->{ $self->conf->{casAttr} || $self->conf->{whatToTrace} };
|| $self->conf->{whatToTrace} };
$self->logger->debug("Get username $username"); $self->logger->debug("Get username $username");
# Get attributes [CAS 3.0] # Get attributes [CAS 3.0]
my $attributes = {}; my $attributes = {};
my $ev = my $ev
( $app and $self->conf->{casAppMetaDataExportedVars}->{$app} ) = ( $app and $self->conf->{casAppMetaDataExportedVars}->{$app} )
? $self->conf->{casAppMetaDataExportedVars}->{$app} ? $self->conf->{casAppMetaDataExportedVars}->{$app}
: {}; : {};
unless (%$ev) { unless (%$ev) {
$ev = $self->conf->{casAttributes} || {}; $ev = $self->conf->{casAttributes} || {};
} }
@ -728,7 +748,7 @@ sub _validate2 {
foreach my $casAttribute ( keys %$ev ) { foreach my $casAttribute ( keys %$ev ) {
my $localSessionValue = $localSession->data->{ $ev->{$casAttribute} }; my $localSessionValue = $localSession->data->{ $ev->{$casAttribute} };
$attributes->{$casAttribute} = $localSessionValue $attributes->{$casAttribute} = $localSessionValue
if defined $localSessionValue; if defined $localSessionValue;
} }
# Return success message # Return success message

View File

@ -217,6 +217,7 @@ sub issuer {
authentication => 'Demo', authentication => 'Demo',
userDB => 'Same', userDB => 'Same',
issuerDBCASActivation => 1, issuerDBCASActivation => 1,
issuerDBCASRule => '$uid eq "french"',
casAttr => 'uid', casAttr => 'uid',
casAccessControlPolicy => 'error', casAccessControlPolicy => 'error',
multiValuesSeparator => ';', multiValuesSeparator => ';',