lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Auth/CAS.pm

283 lines
7.6 KiB
Perl

package Lemonldap::NG::Portal::Auth::CAS;
use strict;
use Mouse;
use URI::Escape;
use Lemonldap::NG::Common::FormEncode;
use Lemonldap::NG::Portal::Main::Constants qw(
PE_ERROR
PE_IDPCHOICE
PE_OK
PE_REDIRECT
PE_SENDRESPONSE
);
our $VERSION = '2.0.12';
extends 'Lemonldap::NG::Portal::Main::Auth', 'Lemonldap::NG::Portal::Lib::CAS';
# PROPERTIES
has srvNumber => ( is => 'rw', default => 0 );
has srvList => ( is => 'rw', default => sub { [] } );
use constant sessionKind => 'CAS';
# INITIALIZATION
sub init {
my ($self) = @_;
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) {
my $name = $_;
$name =
$self->conf->{casSrvMetaDataOptions}->{$_}
->{casSrvMetaDataOptionsDisplayName}
if $self->conf->{casSrvMetaDataOptions}->{$_}
->{casSrvMetaDataOptionsDisplayName};
my $icon = $self->conf->{casSrvMetaDataOptions}->{$_}
->{casSrvMetaDataOptionsIcon};
my $order = $self->conf->{casSrvMetaDataOptions}->{$_}
->{casSrvMetaDataOptionsSortNumber} // 0;
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,
order => $order,
class => "openidconnect",
};
}
@list =
sort {
$a->{order} <=> $b->{order}
or $a->{name} cmp $b->{name}
or $a->{val} cmp $b->{val}
} @list;
$self->srvList( \@list );
return 1;
}
# RUNNING METHODS
sub extractFormInfo {
my ( $self, $req ) = @_;
# Local URL
my $local_url = $self->p->fullUrl($req);
# Remove cancel parameter
$local_url =~ s/cancel=1&?//;
# Catch proxy callback
if ( $req->param('casProxy') ) {
$self->logger->debug("CAS: Proxy callback detected");
my $pgtIou = $req->param('pgtIou');
my $pgtId = $req->param('pgtId');
if ( $pgtIou and $pgtId ) {
# Store pgtId and pgtIou
my $pgtSessionId = $self->storePGT( $pgtIou, $pgtId );
unless ($pgtSessionId) {
$self->userLogger->error(
"CAS: Unable to store pgtIou $pgtIou and pgtId $pgtId");
}
else {
$self->logger->debug(
"CAS: Store pgtIou $pgtIou and pgtId $pgtId in session $pgtSessionId"
);
}
}
# Exit
$req->response( [ 200, [ 'Content-Length' => 0 ], [] ] );
return PE_SENDRESPONSE;
}
my $srv;
# Check Service Ticket
my $ticket = $req->param('ticket');
# 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 ) {
($srv) = keys %{ $self->casSrvList };
$self->logger->debug("Selecting the only defined CAS server: $srv");
}
else {
# Server list
my $portalPath = $self->conf->{portal};
$portalPath =~ s#^https?://[^/]+/?#/#;
$req->data->{list} = $self->srvList;
$req->data->{login} = 1;
return PE_IDPCHOICE;
}
}
my $srvConf = $self->conf->{casSrvMetaDataOptions}->{$srv};
unless ($srvConf) {
$self->userLogger->error("CAS server $srv not configured");
return PE_ERROR;
}
# Provider is choosen
$self->logger->debug("CAS server $srv choosen");
$req->data->{_casSrvCurrent} = $srv;
# Unless a ticket has been found, we redirect the user
unless ($ticket) {
# Add request state parameters
if ( $req->data->{_url} ) {
$local_url .= ( $local_url =~ /\?/ ? '&' : '?' )
. build_urlencoded( url => $req->data->{_url} );
}
# 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};
$self->logger->debug("CAS: Redirect user to $login_url");
$req->{urldc} = $login_url;
$req->steps( [] );
$req->addCookie(
$self->p->cookie(
name => 'llngcasserver',
value => $srv,
secure => $self->conf->{securedCookie},
)
);
return PE_REDIRECT;
}
$self->logger->debug("CAS: Service Ticket received: $ticket");
my $proxied =
$self->conf->{casSrvMetaDataOptionsProxiedServices}->{$srv} || {};
# Ticket found, try to validate it
$local_url =~ s/ticket=[^&]+//;
$local_url =~ s/\?$//;
$local_url =~ s/\&$//;
( $req->{user}, $req->data->{casAttrs} ) =
$self->validateST( $req, $local_url, $ticket, $srvConf, $proxied );
unless ( $req->{user} ) {
$self->userLogger->error("CAS: Unable to validate ST $ticket");
return PE_ERROR;
}
else {
$self->logger->debug("CAS: User $req->{user} found");
}
# Request proxy tickets for proxied services
if (%$proxied) {
# Check we received a PGT
my $pgtId = $req->data->{pgtId};
unless ($pgtId) {
$self->logger->error(
"CAS: Proxy mode activated, but no PGT received");
return PE_ERROR;
}
# Get a proxy ticket for each proxied service
foreach ( keys %$proxied ) {
my $service = $proxied->{$_};
my $pt = $self->retrievePT( $service, $pgtId, $srvConf );
unless ($pt) {
$self->logger->error(
"CAS: No proxy ticket received for service $service");
return PE_ERROR;
}
$self->logger->debug(
"CAS: Received proxy ticket $pt for service $service");
# Store it in session
$req->{sessionInfo}->{ '_casPT' . $_ } = $pt;
}
}
return PE_OK;
}
sub authenticate {
return PE_OK;
}
# Set authenticationLevel.
sub setAuthSessionInfo {
my ( $self, $req ) = @_;
$req->{sessionInfo}->{authenticationLevel} = $self->conf->{casAuthnLevel};
$req->{sessionInfo}->{_casSrv} = $req->data->{_casSrvCurrent};
return PE_OK;
}
sub authLogout {
my ( $self, $req ) = @_;
# Build CAS logout URL
my $logout_url = $self->getServerLogoutURL( $self->p->fullUrl($req),
$self->conf->{casSrvMetaDataOptions}->{ $req->userData->{_casSrv} }
->{casSrvMetaDataOptionsUrl} );
$self->logger->debug("Build CAS logout URL: $logout_url");
# Register CAS logout URL in logoutServices
$req->data->{logoutServices}->{CASserver} = $logout_url;
return PE_OK;
}
sub getDisplayType {
return "logo";
}
1;