2016-12-20 12:53:33 +01:00
|
|
|
package Lemonldap::NG::Portal::Auth::CAS;
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Mouse;
|
|
|
|
use URI::Escape;
|
2017-04-11 19:05:00 +02:00
|
|
|
use Lemonldap::NG::Common::FormEncode;
|
2016-12-20 11:43:22 +01:00
|
|
|
use Lemonldap::NG::Portal::Main::Constants qw(
|
|
|
|
PE_ERROR
|
2018-06-29 17:51:39 +02:00
|
|
|
PE_IDPCHOICE
|
2016-12-20 11:43:22 +01:00
|
|
|
PE_OK
|
|
|
|
PE_REDIRECT
|
|
|
|
PE_SENDRESPONSE
|
|
|
|
);
|
|
|
|
|
2019-09-13 15:17:51 +02:00
|
|
|
our $VERSION = '2.0.6';
|
2016-12-20 11:43:22 +01:00
|
|
|
|
2018-02-19 22:11:43 +01:00
|
|
|
extends 'Lemonldap::NG::Portal::Main::Auth', 'Lemonldap::NG::Portal::Lib::CAS';
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
# PROPERTIES
|
|
|
|
|
2017-04-12 23:11:11 +02:00
|
|
|
has srvNumber => ( is => 'rw', default => 0 );
|
2019-07-02 20:03:40 +02:00
|
|
|
has srvList => ( is => 'rw', default => sub { [] } );
|
2018-06-30 07:44:05 +02:00
|
|
|
use constant sessionKind => 'CAS';
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
# INITIALIZATION
|
|
|
|
|
|
|
|
sub init {
|
|
|
|
my ($self) = @_;
|
2017-09-26 19:50:38 +02:00
|
|
|
|
2017-04-12 23:11:11 +02:00
|
|
|
return 0 unless ( $self->loadSrv );
|
|
|
|
my @tab = ( keys %{ $self->casSrvList } );
|
|
|
|
unless (@tab) {
|
|
|
|
$self->logger->error("No CAS server configured");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
$self->srvNumber( scalar @tab );
|
|
|
|
my @list;
|
|
|
|
my $portalPath = $self->conf->{portal};
|
|
|
|
|
|
|
|
foreach (@tab) {
|
2019-04-10 22:14:46 +02:00
|
|
|
my $name = $_;
|
|
|
|
$name =
|
|
|
|
$self->conf->{casSrvMetaDataOptions}->{$_}
|
|
|
|
->{casSrvMetaDataOptionsDisplayName}
|
|
|
|
if $self->conf->{casSrvMetaDataOptions}->{$_}
|
2017-04-12 23:11:11 +02:00
|
|
|
->{casSrvMetaDataOptionsDisplayName};
|
|
|
|
my $icon = $self->conf->{casSrvMetaDataOptions}->{$_}
|
|
|
|
->{casSrvMetaDataOptionsIcon};
|
2019-04-10 21:58:28 +02:00
|
|
|
my $order = $self->conf->{casSrvMetaDataOptions}->{$_}
|
2019-04-10 22:14:46 +02:00
|
|
|
->{casSrvMetaDataOptionsSortNumber} // 0;
|
2017-04-12 23:11:11 +02:00
|
|
|
my $img_src;
|
|
|
|
|
|
|
|
if ($icon) {
|
|
|
|
$img_src =
|
|
|
|
( $icon =~ m#^https?://# )
|
|
|
|
? $icon
|
|
|
|
: $portalPath . $self->p->staticPrefix . "/common/" . $icon;
|
|
|
|
}
|
|
|
|
push @list,
|
|
|
|
{
|
|
|
|
val => $_,
|
|
|
|
name => $name,
|
|
|
|
icon => $img_src,
|
2019-04-10 21:58:28 +02:00
|
|
|
order => $order,
|
2017-04-12 23:11:11 +02:00
|
|
|
class => "openidconnect",
|
|
|
|
};
|
|
|
|
}
|
2019-04-10 21:58:28 +02:00
|
|
|
@list =
|
|
|
|
sort {
|
|
|
|
$a->{order} <=> $b->{order}
|
|
|
|
or $a->{name} cmp $b->{name}
|
|
|
|
or $a->{val} cmp $b->{val}
|
|
|
|
} @list;
|
2017-04-12 23:11:11 +02:00
|
|
|
$self->srvList( \@list );
|
2016-12-21 06:32:38 +01:00
|
|
|
return 1;
|
2016-12-20 11:43:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# RUNNING METHODS
|
|
|
|
|
|
|
|
sub extractFormInfo {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
|
|
|
|
# Local URL
|
2016-12-22 09:40:50 +01:00
|
|
|
my $local_url = $self->p->fullUrl($req);
|
2016-12-20 11:43:22 +01:00
|
|
|
|
2019-09-13 15:17:51 +02:00
|
|
|
# Remove cancel parameter
|
2019-09-13 15:35:05 +02:00
|
|
|
$local_url =~ s/cancel=1&?//;
|
2019-09-13 15:17:51 +02:00
|
|
|
|
2016-12-20 11:43:22 +01:00
|
|
|
# Catch proxy callback
|
|
|
|
if ( $req->param('casProxy') ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("CAS: Proxy callback detected");
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
my $pgtIou = $req->param('pgtIou');
|
|
|
|
my $pgtId = $req->param('pgtId');
|
|
|
|
|
|
|
|
if ( $pgtIou and $pgtId ) {
|
|
|
|
|
|
|
|
# Store pgtId and pgtIou
|
2017-03-28 20:23:50 +02:00
|
|
|
my $pgtSessionId = $self->storePGT( $pgtIou, $pgtId );
|
|
|
|
unless ($pgtSessionId) {
|
2017-02-19 12:51:58 +01:00
|
|
|
$self->userLogger->error(
|
2017-03-28 20:23:50 +02:00
|
|
|
"CAS: Unable to store pgtIou $pgtIou and pgtId $pgtId");
|
2016-12-20 11:43:22 +01:00
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
2017-03-28 20:23:50 +02:00
|
|
|
"CAS: Store pgtIou $pgtIou and pgtId $pgtId in session $pgtSessionId"
|
|
|
|
);
|
2016-12-20 11:43:22 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
# Exit
|
|
|
|
$req->response( [ 200, [ 'Content-Length' => 0 ], [] ] );
|
|
|
|
return PE_SENDRESPONSE;
|
|
|
|
}
|
|
|
|
|
2017-04-12 23:11:11 +02:00
|
|
|
my $srv;
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
# Check Service Ticket
|
|
|
|
my $ticket = $req->param('ticket');
|
|
|
|
|
2017-04-12 23:11:11 +02:00
|
|
|
# Check if CAS server is chosen
|
|
|
|
unless ( ( $ticket and $srv = $req->cookies->{llngcasserver} )
|
|
|
|
or $srv = $req->param('idp') )
|
|
|
|
{
|
|
|
|
$self->logger->debug("Redirecting user to CAS server list");
|
|
|
|
|
|
|
|
# Auto select provider if there is only one
|
|
|
|
if ( $self->srvNumber == 1 ) {
|
2017-04-13 09:28:15 +02:00
|
|
|
($srv) = keys %{ $self->casSrvList };
|
2017-04-12 23:11:11 +02:00
|
|
|
$self->logger->debug("Selecting the only defined CAS server: $srv");
|
|
|
|
}
|
|
|
|
|
|
|
|
else {
|
|
|
|
|
|
|
|
# Server list
|
2017-04-16 11:47:32 +02:00
|
|
|
my $portalPath = $self->conf->{portal};
|
2017-04-12 23:11:11 +02:00
|
|
|
$portalPath =~ s#^https?://[^/]+/?#/#;
|
|
|
|
|
2020-02-20 23:34:02 +01:00
|
|
|
$req->data->{list} = $self->srvList;
|
2017-04-12 23:11:11 +02:00
|
|
|
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{login} = 1;
|
2018-06-29 17:51:39 +02:00
|
|
|
return PE_IDPCHOICE;
|
2017-04-12 23:11:11 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-13 09:28:15 +02:00
|
|
|
my $srvConf = $self->conf->{casSrvMetaDataOptions}->{$srv};
|
2017-04-12 23:11:11 +02:00
|
|
|
unless ($srvConf) {
|
|
|
|
$self->userLogger->error("CAS server $srv not configured");
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Provider is choosen
|
|
|
|
$self->logger->debug("CAS server $srv choosen");
|
|
|
|
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{_casSrvCurrent} = $srv;
|
2017-04-12 23:11:11 +02:00
|
|
|
|
2016-12-20 11:43:22 +01:00
|
|
|
# Unless a ticket has been found, we redirect the user
|
|
|
|
unless ($ticket) {
|
2018-06-13 12:23:03 +02:00
|
|
|
|
|
|
|
# Add request state parameters
|
2018-07-05 22:56:16 +02:00
|
|
|
if ( $req->data->{_url} ) {
|
2018-06-13 12:23:03 +02:00
|
|
|
$local_url .= ( $local_url =~ /\?/ ? '&' : '?' )
|
2018-07-05 22:56:16 +02:00
|
|
|
. build_urlencoded( url => $req->data->{_url} );
|
2018-06-13 12:23:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Forward hidden fields
|
|
|
|
if ( $req->{portalHiddenFormValues}
|
|
|
|
and %{ $req->{portalHiddenFormValues} } )
|
|
|
|
{
|
|
|
|
|
|
|
|
$self->logger->debug("Add hidden values to CAS redirect URL\n");
|
|
|
|
$local_url .= ( $local_url =~ /\?/ ? '&' : '?' )
|
|
|
|
. build_urlencoded( %{ $req->{portalHiddenFormValues} } );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Build login URL
|
|
|
|
my $login_url = $self->getServerLoginURL( $local_url, $srvConf );
|
|
|
|
$login_url .= '&renew=true' if $srvConf->{casSrvMetaDataOptionsRenew};
|
|
|
|
$login_url .= '&gateway=true'
|
|
|
|
if $srvConf->{casSrvMetaDataOptionsGateway};
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("CAS: Redirect user to $login_url");
|
2016-12-20 11:43:22 +01:00
|
|
|
$req->{urldc} = $login_url;
|
|
|
|
$req->steps( [] );
|
2017-04-12 23:11:11 +02:00
|
|
|
$req->addCookie(
|
|
|
|
$self->p->cookie(
|
|
|
|
name => 'llngcasserver',
|
|
|
|
value => $srv,
|
|
|
|
secure => $self->conf->{securedCookie},
|
|
|
|
)
|
|
|
|
);
|
2016-12-20 11:43:22 +01:00
|
|
|
return PE_REDIRECT;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("CAS: Service Ticket received: $ticket");
|
2016-12-20 11:43:22 +01:00
|
|
|
|
2018-06-19 16:47:09 +02:00
|
|
|
my $proxied =
|
|
|
|
$self->conf->{casSrvMetaDataOptionsProxiedServices}->{$srv} || {};
|
|
|
|
|
2016-12-20 11:43:22 +01:00
|
|
|
# Ticket found, try to validate it
|
2017-03-29 21:43:10 +02:00
|
|
|
$local_url =~ s/ticket=[^&]+//;
|
|
|
|
$local_url =~ s/\?$//;
|
2018-06-13 12:23:03 +02:00
|
|
|
$local_url =~ s/\&$//;
|
2018-07-05 22:56:16 +02:00
|
|
|
( $req->{user}, $req->data->{casAttrs} ) =
|
2018-06-19 16:47:09 +02:00
|
|
|
$self->validateST( $req, $local_url, $ticket, $srvConf, $proxied );
|
2017-04-11 19:05:02 +02:00
|
|
|
unless ( $req->{user} ) {
|
2017-03-28 20:23:50 +02:00
|
|
|
$self->userLogger->error("CAS: Unable to validate ST $ticket");
|
2016-12-20 11:43:22 +01:00
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("CAS: User $req->{user} found");
|
2016-12-20 11:43:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# Request proxy tickets for proxied services
|
2017-04-14 09:40:01 +02:00
|
|
|
if (%$proxied) {
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
# Check we received a PGT
|
2018-07-05 22:56:16 +02:00
|
|
|
my $pgtId = $req->data->{pgtId};
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
unless ($pgtId) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
|
|
|
"CAS: Proxy mode activated, but no PGT received");
|
2016-12-20 11:43:22 +01:00
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get a proxy ticket for each proxied service
|
2017-04-13 20:54:06 +02:00
|
|
|
foreach ( keys %$proxied ) {
|
|
|
|
my $service = $proxied->{$_};
|
2019-07-02 20:03:40 +02:00
|
|
|
my $pt = $self->retrievePT( $service, $pgtId, $srvConf );
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
unless ($pt) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->error(
|
2017-04-15 15:21:33 +02:00
|
|
|
"CAS: No proxy ticket received for service $service");
|
2016-12-20 11:43:22 +01:00
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"CAS: Received proxy ticket $pt for service $service");
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
# Store it in session
|
|
|
|
$req->{sessionInfo}->{ '_casPT' . $_ } = $pt;
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub authenticate {
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Set authenticationLevel.
|
|
|
|
sub setAuthSessionInfo {
|
|
|
|
my ( $self, $req ) = @_;
|
2017-04-13 09:28:15 +02:00
|
|
|
$req->{sessionInfo}->{authenticationLevel} = $self->conf->{casAuthnLevel};
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->{sessionInfo}->{_casSrv} = $req->data->{_casSrvCurrent};
|
2016-12-20 11:43:22 +01:00
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub authLogout {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
|
|
|
|
# Build CAS logout URL
|
2017-09-11 17:26:44 +02:00
|
|
|
my $logout_url = $self->getServerLogoutURL( $self->p->fullUrl($req),
|
2017-04-13 09:28:15 +02:00
|
|
|
$self->conf->{casSrvMetaDataOptions}->{ $req->userData->{_casSrv} }
|
2017-09-11 17:26:44 +02:00
|
|
|
->{casSrvMetaDataOptionsUrl} );
|
2016-12-20 11:43:22 +01:00
|
|
|
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Build CAS logout URL: $logout_url");
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
# Register CAS logout URL in logoutServices
|
2018-07-05 22:56:16 +02:00
|
|
|
$req->data->{logoutServices}->{CASserver} = $logout_url;
|
2016-12-20 11:43:22 +01:00
|
|
|
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
sub getDisplayType {
|
|
|
|
return "logo";
|
|
|
|
}
|
2016-12-20 12:53:33 +01:00
|
|
|
|
|
|
|
1;
|