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

296 lines
8.0 KiB
Perl
Raw Normal View History

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_OK
2022-05-19 23:20:32 +02:00
PE_ERROR
2016-12-20 11:43:22 +01:00
PE_REDIRECT
2022-05-19 23:20:32 +02:00
PE_IDPCHOICE
2016-12-20 11:43:22 +01:00
PE_SENDRESPONSE
);
2022-05-19 23:20:32 +02:00
our $VERSION = '2.0.15';
2016-12-20 11:43:22 +01:00
2022-05-19 23:20:32 +02:00
extends qw(
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 { [] } );
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};
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,
order => $order,
2017-04-12 23:11:11 +02:00
class => "openidconnect",
};
}
@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 );
2021-02-01 22:30:37 +01:00
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
# Remove cancel parameter
2019-09-13 15:35:05 +02:00
$local_url =~ s/cancel=1&?//;
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 {
2022-05-19 23:20:32 +02:00
# Try to use server resolution rules
foreach ( keys %{ $self->srvRules } ) {
my $cond = $self->srvRules->{$_} or next;
if ( $cond->( $req, $req->sessionInfo ) ) {
$self->logger->debug(
"CAS Server $_ selected from resolution rule");
$srv = $_;
last;
}
}
2017-04-12 23:11:11 +02:00
unless ($srv) {
2017-04-12 23:11:11 +02:00
# Server list
2022-05-19 23:20:32 +02:00
$req->data->{list} = $self->srvList;
$req->data->{login} = 1;
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");
$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) {
# 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};
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
$local_url =~ s/ticket=[^&]+//;
$local_url =~ s/\?$//;
$local_url =~ s/\&$//;
( $req->{user}, $req->data->{casAttrs} ) =
2018-06-19 16:47:09 +02:00
$self->validateST( $req, $local_url, $ticket, $srvConf, $proxied );
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
if (%$proxied) {
2016-12-20 11:43:22 +01:00
# Check we received a PGT
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 {
2021-02-01 22:30:37 +01:00
return PE_OK;
2016-12-20 11:43:22 +01:00
}
# Set authenticationLevel.
sub setAuthSessionInfo {
my ( $self, $req ) = @_;
2017-04-13 09:28:15 +02:00
$req->{sessionInfo}->{authenticationLevel} = $self->conf->{casAuthnLevel};
2022-02-16 17:43:29 +01:00
$req->{sessionInfo}->{_casSrv} = $req->data->{_casSrvCurrent};
2021-02-01 22:30:37 +01:00
return PE_OK;
2016-12-20 11:43:22 +01:00
}
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
$req->data->{logoutServices}->{CASserver} = $logout_url;
2016-12-20 11:43:22 +01:00
2021-02-01 22:30:37 +01:00
return PE_OK;
2016-12-20 11:43:22 +01:00
}
sub getDisplayType {
return "logo";
}
2016-12-20 12:53:33 +01:00
1;