lemonldap-ng/lemonldap-ng-portal/lib/Lemonldap/NG/Portal/Main/Run.pm

1316 lines
39 KiB
Perl
Raw Normal View History

2016-03-30 21:51:12 +02:00
##@class Lemonldap::NG::Portal::Main::Run
# Serve request part of Lemonldap::NG portal
#
2016-04-03 08:33:50 +02:00
# Parts of this file:
# - response handler
# - main entry points
# - running methods
# - utilities
2016-03-31 07:27:59 +02:00
#
2016-03-29 23:09:55 +02:00
package Lemonldap::NG::Portal::Main::Run;
2022-06-18 17:54:07 +02:00
our $VERSION = '2.0.15';
2016-03-29 23:09:55 +02:00
2016-04-07 23:31:56 +02:00
package Lemonldap::NG::Portal::Main;
2016-04-03 18:27:13 +02:00
2016-04-07 23:31:56 +02:00
use strict;
use URI::Escape;
use URI;
2019-02-21 16:25:02 +01:00
use JSON;
2021-09-23 17:36:36 +02:00
use Lemonldap::NG::Common::Util qw(getPSessionID getSameSite);
2016-03-30 21:51:12 +02:00
has trOverCache => ( is => 'rw', default => sub { {} } );
# The execution order between groups and macros can be
# modified in config (#1877)
sub groupsAndMacros {
return (
$_[0]->conf->{groupsBeforeMacros}
? qw(setGroups setMacros)
: qw(setMacros setGroups)
);
}
2016-04-03 08:33:50 +02:00
# List constants
2019-02-28 17:40:15 +01:00
sub authProcess { qw(extractFormInfo getUser authenticate) }
2016-04-03 08:33:50 +02:00
sub sessionData {
2020-02-20 23:34:02 +01:00
return qw(setAuthSessionInfo setSessionInfo), $_[0]->groupsAndMacros,
qw(setPersistentSessionInfo setLocalGroups store secondFactor);
}
sub validSession {
qw(storeHistory buildCookie);
2016-04-03 08:33:50 +02:00
}
# RESPONSE HANDLER
# ----------------
#
# - replace Lemonldap::NG::Common::PSGI::Request request by
# Lemonldap::NG::Portal::Main::Request
# - launch Lemonldap::NG::Common::PSGI::Request::handler()
2016-03-30 21:51:12 +02:00
sub handler {
2016-04-03 18:51:23 +02:00
my ( $self, $req ) = @_;
2018-07-04 13:33:20 +02:00
2016-03-30 21:51:15 +02:00
bless $req, 'Lemonldap::NG::Portal::Main::Request';
2018-12-17 18:43:54 +01:00
$req->init( $self->conf );
my $sp = 0;
2018-07-04 13:33:20 +02:00
# Restore pdata
if ( my $v = $req->cookies->{ $self->conf->{cookieName} . 'pdata' } ) {
$sp = 1;
eval { $req->pdata( JSON::from_json( uri_unescape($v) ) ); };
2018-07-04 13:33:20 +02:00
if ($@) {
$self->logger->error("Bad JSON content in cookie pdata");
$req->pdata( {} );
}
# Avoid fatal errors when using old keepPdata format
if ( $req->pdata->{keepPdata}
and not( ref $req->pdata->{keepPdata} eq "ARRAY" ) )
{
$req->pdata->{keepPdata} = [];
}
2018-07-04 13:33:20 +02:00
}
my $res = $self->Lemonldap::NG::Common::PSGI::Router::handler($req);
# Avoid permanent loop 'Portal <-> _url' if pdata cookie is not removed
2020-01-18 18:58:42 +01:00
if ( $req->userData->{_url}
and !$req->pdata->{keepPdata}
and $req->userData->{_session_id}
2019-08-29 10:45:47 +02:00
and $req->{env}->{HTTP_COOKIE}
2020-01-18 18:58:42 +01:00
and $req->{env}->{HTTP_COOKIE} eq
encode_base64( $req->userData->{_url}, '' ) )
{
2020-01-08 23:06:49 +01:00
$self->logger->info("Force cleaning pdata");
$self->logger->warn("pdata cookie domain must be set")
unless ( $self->conf->{pdataDomain} );
$req->pdata( {} );
}
2018-07-04 13:33:20 +02:00
# Save pdata
if ( $sp or %{ $req->pdata } ) {
my %v = (
name => $self->conf->{cookieName} . 'pdata',
secure => $self->conf->{securedCookie},
2019-02-28 17:40:15 +01:00
(
%{ $req->pdata }
? ( value => uri_escape( JSON::to_json( $req->pdata ) ) )
: ( value => '', expires => 'Wed, 21 Oct 2015 00:00:00 GMT' )
),
(
$self->conf->{pdataDomain}
? ( domain => $self->conf->{pdataDomain}, )
: ()
),
);
push @{ $res->[1] }, 'Set-Cookie', $self->cookie(%v);
}
2018-07-04 13:33:20 +02:00
return $res;
2016-03-30 21:51:12 +02:00
}
2016-04-03 08:33:50 +02:00
# MAIN ENTRY POINTS (declared in Lemonldap::NG::Portal::Main::Init)
# -----------------
#
# Entry points:
2017-01-04 23:19:17 +01:00
# - "/ping": - authenticated() for already authenticated users
2016-04-03 08:33:50 +02:00
# - pleaseAuth() for others
# - "/": - login() ~first access
# - postLogin(), same for POST requests
# - authenticatedRequest() for authenticated users
2016-03-31 07:27:59 +02:00
sub authenticated {
my ( $self, $req ) = @_;
2022-02-25 17:07:05 +01:00
return $self->do(
$req,
[
'importHandlerData',
'controlUrl',
@{ $self->forAuthUser },
sub {
2022-06-23 12:12:25 +02:00
$req->response(
$self->sendJSONresponse( $req, { status => 1 } ) );
2022-02-25 17:07:05 +01:00
return PE_SENDRESPONSE;
}
]
);
2016-03-31 07:27:59 +02:00
}
sub pleaseAuth {
my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { status => 0 } );
}
sub login {
my ( $self, $req ) = @_;
2016-04-03 18:27:22 +02:00
return $self->do(
2016-04-01 07:24:27 +02:00
$req,
2019-02-28 17:40:15 +01:00
[
'checkUnauthLogout', 'controlUrl', # Fix 2342
2020-12-05 19:31:23 +01:00
@{ $self->beforeAuth }, $self->authProcess,
@{ $self->betweenAuthAndData }, $self->sessionData,
@{ $self->afterData }, $self->validSession,
@{ $self->endAuth }
2016-03-31 22:08:43 +02:00
]
2016-03-31 07:27:59 +02:00
);
}
sub postLogin {
my ( $self, $req ) = @_;
2016-04-03 18:27:22 +02:00
return $self->do(
2016-04-01 07:24:27 +02:00
$req,
2019-02-28 17:40:15 +01:00
[
'checkUnauthLogout', 'restoreArgs', # Fix 2342
2020-12-05 19:31:23 +01:00
'controlUrl', @{ $self->beforeAuth },
$self->authProcess, @{ $self->betweenAuthAndData },
$self->sessionData, @{ $self->afterData },
$self->validSession, @{ $self->endAuth }
2016-03-31 22:08:43 +02:00
]
2016-03-31 07:27:59 +02:00
);
}
sub authenticatedRequest {
2016-03-31 22:08:43 +02:00
my ( $self, $req ) = @_;
$req->data->{alreadyAuthenticated} = 1;
2016-04-14 21:49:27 +02:00
return $self->do(
$req,
2019-02-28 17:40:15 +01:00
[
'importHandlerData', 'controlUrl',
2018-07-05 23:00:40 +02:00
'checkLogout', @{ $self->forAuthUser }
2016-04-14 21:49:27 +02:00
]
);
2016-03-31 22:08:43 +02:00
}
2016-04-13 07:32:10 +02:00
sub postAuthenticatedRequest {
my ( $self, $req ) = @_;
2016-04-14 20:42:59 +02:00
return $self->do(
$req,
2019-02-28 17:40:15 +01:00
[
'importHandlerData', 'restoreArgs',
2018-07-05 23:00:40 +02:00
'controlUrl', 'checkLogout',
2016-04-14 21:49:27 +02:00
@{ $self->forAuthUser }
2016-04-14 20:42:59 +02:00
]
);
2016-04-13 07:32:10 +02:00
}
sub refresh {
my ( $self, $req ) = @_;
$req->mustRedirect(1);
my %data = %{ $req->userData };
2019-12-19 18:11:33 +01:00
$self->userLogger->notice(
'Refresh request for ' . $data{ $self->conf->{whatToTrace} } );
2019-12-15 12:54:28 +01:00
$req->user( $data{_user} || $data{ $self->conf->{whatToTrace} } );
$req->id( $data{_session_id} );
foreach ( keys %data ) {
# Variables that start with _ are kept accross refresh
if (/^_/) {
# But not OIDC tokens, which can be refreshed
delete $data{$_}
if (
/^(_oidc_access_token|_oidc_refresh_token|_oidc_access_token_eol)$/
);
}
# Other variables should be refreshed
else {
# But not these two
if (/^(?:startTime|authenticationLevel)$/) {
next;
}
else {
delete $data{$_};
}
}
}
2019-05-26 11:56:58 +02:00
$data{_updateTime} = strftime( "%Y%m%d%H%M%S", localtime() );
$self->logger->debug(
"Set session $req->{id} _updateTime with $data{_updateTime}");
2019-02-28 17:40:15 +01:00
$req->steps( [
2019-12-15 12:54:28 +01:00
'getUser',
@{ $self->betweenAuthAndData },
'setSessionInfo',
sub {
$_[0]->sessionInfo->{$_} = $data{$_} foreach ( keys %data );
$_[0]->refresh(1);
return PE_OK;
},
2021-11-17 10:03:45 +01:00
$self->groupsAndMacros,
'setLocalGroups',
2021-11-22 20:40:34 +01:00
'store'
]
);
2017-05-04 09:13:26 +02:00
my $res = $req->error( $self->process($req) );
if ($res) {
$req->info(
$self->loadTemplate(
$req,
2019-02-28 17:40:15 +01:00
'simpleInfo', params => { trspan => 'rightsReloadNeedsLogout' }
)
2017-05-04 09:13:26 +02:00
);
2017-05-04 09:19:50 +02:00
$req->urldc( $self->conf->{portal} );
2019-02-28 17:40:15 +01:00
return $self->do( $req, [ sub { PE_INFO } ] );
2017-05-04 09:13:26 +02:00
}
2019-02-28 17:40:15 +01:00
return $self->do( $req, [ sub { PE_OK } ] );
}
2016-04-18 22:23:40 +02:00
sub logout {
my ( $self, $req ) = @_;
2016-05-23 18:55:23 +02:00
return $self->do(
$req,
2019-02-28 17:40:15 +01:00
[
2019-03-26 08:06:55 +01:00
'importHandlerData', 'controlUrl',
@{ $self->beforeLogout }, 'authLogout',
'deleteSession'
2016-05-23 18:55:23 +02:00
]
);
2016-04-18 22:23:40 +02:00
}
sub unauthLogout {
my ( $self, $req ) = @_;
$self->userLogger->info('Unauthenticated logout request');
$self->logger->debug('Cleaning pdata');
$self->logger->debug("Removing $self->{conf}->{cookieName} cookie");
$req->pdata( {} );
$req->addCookie(
$self->cookie(
name => $self->conf->{cookieName},
domain => $self->conf->{domain},
secure => $self->conf->{securedCookie},
expires => 'Wed, 21 Oct 2015 00:00:00 GMT',
value => 0
)
);
return $self->do( $req, [ sub { PE_LOGOUT_OK } ] );
}
2016-04-03 08:33:50 +02:00
# RUNNING METHODS
# ---------------
2016-03-31 22:08:43 +02:00
sub do {
2016-04-01 07:24:27 +02:00
my ( $self, $req, $steps ) = @_;
2016-03-31 22:08:43 +02:00
$req->steps($steps);
$req->data->{activeTimer} = $self->conf->{activeTimer};
2016-04-11 07:00:34 +02:00
my $err = $req->error( $self->process($req) );
2016-04-01 07:24:27 +02:00
2017-02-19 08:17:48 +01:00
# Update status
2017-02-18 15:25:51 +01:00
if ( my $p = $self->HANDLER->tsv->{statusPipe} ) {
2019-02-28 17:40:15 +01:00
$p->print( ( $req->user ? $req->user : $req->address ) . ' => '
. $req->uri
. " $err\n" );
2017-02-18 15:25:51 +01:00
}
2017-02-19 08:17:48 +01:00
# Update history
return $req->response if $err == PE_SENDRESPONSE;
# Remove userData if authentication fails
$req->userData( {} ) if ( $err == PE_BADCREDENTIALS or $err == PE_BADOTP );
2016-04-01 07:24:27 +02:00
if ( !$self->conf->{noAjaxHook} and $req->wantJSON ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug('Processing to JSON response');
2017-02-17 08:40:15 +01:00
if ( ( $err > 0 and !$req->id ) or $err eq PE_SESSIONNOTGRANTED ) {
my $json = { result => 0, error => $err };
if ( $req->wantErrorRender ) {
$json->{html} = $self->loadTemplate(
$req,
'errormsg',
params => {
AUTH_ERROR => $err,
AUTH_ERROR_TYPE => $req->error_type,
AUTH_ERROR_ROLE => $req->error_role,
}
);
}
return $self->sendJSONresponse(
$req, $json,
code => 401,
headers => [
'WWW-Authenticate' => "SSO " . $self->conf->{portal},
"Content-Type" => "application/json"
],
);
2016-03-31 22:08:43 +02:00
}
2019-06-24 23:06:15 +02:00
elsif ( $err > 0 and $err != PE_PASSWORD_OK and $err != PE_LOGOUT_OK ) {
2016-07-11 23:02:32 +02:00
return $self->sendJSONresponse(
$req,
{
result => 0,
error => $err,
(
$err == PE_NOTIFICATION && $req->id
? ( ciphered_id => $req->id )
: ()
)
},
2016-07-11 23:02:32 +02:00
code => 400
);
}
2016-03-31 22:08:43 +02:00
else {
my $res = { result => 1, error => $err };
unless ( $req->data->{alreadyAuthenticated} ) {
$res->{id} = $req->id;
$res->{id_http} = $req->sessionInfo->{_httpSession}
if $req->sessionInfo->{_httpSession};
}
return $self->sendJSONresponse( $req, $res );
2016-03-31 22:08:43 +02:00
}
}
else {
2019-02-28 17:40:15 +01:00
if (
2019-04-01 17:17:06 +02:00
$req->info
or (
$err
and $err != PE_LOGOUT_OK
and (
$err != PE_REDIRECT
or ( $err == PE_REDIRECT
and $req->data->{redirectFormMethod}
and $req->data->{redirectFormMethod} eq 'post' )
)
2016-11-22 21:55:10 +01:00
)
2019-02-28 17:40:15 +01:00
)
2016-11-22 21:55:10 +01:00
{
my ( $tpl, $prms ) = $self->display($req);
2017-02-15 07:41:50 +01:00
$self->logger->debug("Calling sendHtml with template $tpl");
return $self->sendHtml( $req, $tpl, params => $prms );
2016-03-31 22:08:43 +02:00
}
else {
2017-02-15 07:41:50 +01:00
$self->logger->debug('Calling autoredirect');
2016-03-31 22:08:43 +02:00
return $self->autoRedirect($req);
}
}
}
2016-04-03 08:33:50 +02:00
# Utilities
# ---------
sub getModule {
my ( $self, $req, $type ) = @_;
if ( my $val =
$req->userData->{ { auth => '_auth', user => '_userDB' }->{$type} } )
{
return $val;
}
2019-02-28 17:40:15 +01:00
if (
my $mod = {
2016-04-03 08:33:50 +02:00
auth => '_authentication',
user => '_userDB',
password => '_passwordDB'
}->{$type}
2019-02-28 17:40:15 +01:00
)
2016-04-03 08:33:50 +02:00
{
2016-07-02 10:51:00 +02:00
if ( my $sub = $self->$mod->can('name') ) {
return $sub->( $self->$mod, $req, $type );
2016-04-03 08:33:50 +02:00
}
else {
2016-05-24 07:05:51 +02:00
my $s = ref( $self->$mod );
2019-02-28 17:40:15 +01:00
$s =~
s/^Lemonldap::NG::Portal::(?:(?:Issuer|UserDB|Auth|Password)::)?//;
2016-05-24 07:05:51 +02:00
return $s;
2016-04-03 08:33:50 +02:00
}
}
elsif ( $type eq 'issuer' ) {
return $req->{_activeIssuerDB};
}
else {
die "Unknown type $type";
2016-03-31 22:08:43 +02:00
}
2016-03-31 07:27:59 +02:00
}
2016-04-03 10:44:58 +02:00
sub autoRedirect {
my ( $self, $req ) = @_;
# Set redirection URL if needed
2017-03-16 21:19:06 +01:00
$req->{urldc} ||= $self->conf->{portal}
2019-02-28 17:40:15 +01:00
if ( $req->mustRedirect and not( $req->info ) );
2016-04-03 10:44:58 +02:00
# Redirection should be made if urldc defined
if ( $req->{urldc} ) {
$self->logger->debug("Building redirection to $req->{urldc}");
if ( $self->_jsRedirect->( $req, $req->sessionInfo ) ) {
2016-05-26 23:26:47 +02:00
$req->error(PE_REDIRECT);
$req->data->{redirectFormMethod} = "get";
2016-05-26 23:26:47 +02:00
}
else {
2020-04-22 15:37:19 +02:00
if ( $req->{pdata}->{_url}
and $req->{pdata}->{_url} eq encode_base64( $req->{urldc}, '' )
)
{
2020-01-17 22:27:03 +01:00
$self->logger->info("Force cleaning pdata");
delete $req->{pdata}->{_url};
2020-01-17 22:27:03 +01:00
}
2019-08-28 00:36:18 +02:00
return [ 302, [ Location => $req->{urldc}, $req->spliceHdrs ], [] ];
2016-05-26 23:26:47 +02:00
}
2016-04-03 10:44:58 +02:00
}
2016-05-26 23:26:47 +02:00
my ( $tpl, $prms ) = $self->display($req);
2017-02-15 07:41:50 +01:00
$self->logger->debug("Calling sendHtml with template $tpl");
2016-05-26 23:26:47 +02:00
return $self->sendHtml( $req, $tpl, params => $prms );
2016-04-03 10:44:58 +02:00
}
# Try to recover the session corresponding to id and return session data.
2017-01-24 06:10:57 +01:00
# If $id is set to undef or if $args{force} is true, return a new session.
2016-04-04 22:39:22 +02:00
sub getApacheSession {
2017-01-24 06:10:57 +01:00
my ( $self, $id, %args ) = @_;
$args{kind} //= "SSO";
2016-06-09 13:45:10 +02:00
if ($id) {
2017-02-15 07:41:50 +01:00
$self->logger->debug("Try to get $args{kind} session $id");
2016-06-09 13:45:10 +02:00
}
else {
2017-02-15 07:41:50 +01:00
$self->logger->debug("Try to get a new $args{kind} session");
2016-06-09 13:45:10 +02:00
}
2019-02-28 17:40:15 +01:00
my $as = Lemonldap::NG::Common::Session->new( {
storageModule => $self->conf->{globalStorage},
2016-04-04 22:39:22 +02:00
storageModuleOptions => $self->conf->{globalStorageOptions},
cacheModule => $self->conf->{localSessionStorage},
cacheModuleOptions => $self->conf->{localSessionStorageOptions},
id => $id,
2017-01-24 06:10:57 +01:00
force => $args{force},
kind => $args{kind},
( $args{info} ? ( info => $args{info} ) : () ),
2016-04-04 22:39:22 +02:00
}
);
2017-01-17 21:38:22 +01:00
if ( my $err = $as->error ) {
$self->lmLog(
$err,
2019-02-28 17:40:15 +01:00
(
$err =~ /(?:Object does not exist|Invalid session ID)/
? 'notice'
: 'error'
)
);
2016-04-04 22:39:22 +02:00
return;
}
2017-01-24 06:10:57 +01:00
if ( $id and !$args{force} and !$as->data ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug("Session $args{kind} $id not found");
2016-04-04 22:39:22 +02:00
return;
}
$self->logger->debug("Get session $id from Portal::Main::Run")
if ($id);
$self->logger->debug(
"Check session validity -> " . $self->conf->{timeoutActivity} . "s" )
2019-02-28 17:40:15 +01:00
if ( $self->conf->{timeoutActivity} );
2016-06-09 13:45:10 +02:00
my $now = time;
2019-02-28 17:40:15 +01:00
if (
$id
2016-07-22 11:47:50 +02:00
and defined $as->data->{_utime}
2016-06-09 13:45:10 +02:00
and (
2018-10-22 22:28:37 +02:00
( ( $now - $as->data->{_utime} ) > $self->conf->{timeout} )
2019-02-28 17:40:15 +01:00
or (
$self->conf->{timeoutActivity}
2016-06-09 13:45:10 +02:00
and $as->data->{_lastSeen}
2019-02-28 17:40:15 +01:00
and ( ( $now - $as->data->{_lastSeen} ) >
$self->conf->{timeoutActivity} )
)
)
2019-02-28 17:40:15 +01:00
)
2016-06-09 13:45:10 +02:00
{
2017-02-15 07:41:50 +01:00
$self->logger->debug("Session $args{kind} $id expired");
2016-06-09 13:45:10 +02:00
return;
}
2017-02-15 07:41:50 +01:00
$self->logger->debug( "Return $args{kind} session " . $as->id );
2016-06-09 13:45:10 +02:00
2016-04-04 22:39:22 +02:00
return $as;
}
# Try to recover the persistent session corresponding to uid and return session data.
2016-04-04 22:39:22 +02:00
sub getPersistentSession {
my ( $self, $uid, $info ) = @_;
2016-04-04 22:39:22 +02:00
return
unless ( defined $uid
and !$self->conf->{disablePersistentStorage} );
2016-04-04 22:39:22 +02:00
# Compute persistent identifier
my $pid = getPSessionID($uid);
2016-04-04 22:39:22 +02:00
$info->{_session_uid} = $uid;
2019-02-28 17:40:15 +01:00
my $ps = Lemonldap::NG::Common::Session->new( {
storageModule => $self->conf->{persistentStorage},
2016-04-05 22:46:11 +02:00
storageModuleOptions => $self->conf->{persistentStorageOptions},
2016-04-04 22:39:22 +02:00
id => $pid,
force => 1,
kind => "Persistent",
( $info ? ( info => $info ) : () ),
2016-04-04 22:39:22 +02:00
}
);
if ( $ps->error ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug( $ps->error );
2016-04-04 22:39:22 +02:00
}
else {
2016-04-04 22:39:22 +02:00
# Set _session_uid if not already present
unless ( defined $ps->data->{_session_uid} ) {
$ps->update( { _session_uid => $uid } );
}
2016-04-04 22:39:22 +02:00
# Set _utime if not already present
unless ( defined $ps->data->{_utime} ) {
$ps->update( { _utime => time } );
}
2016-04-04 22:39:22 +02:00
}
return $ps;
}
2016-06-01 19:36:51 +02:00
# Update persistent session.
# Call updateSession() and store %$infos in a persistent session.
# Note that if the session does not exists, it will be created.
# @param infos hash reference of information to update
# @param uid optional Unhashed persistent session ID
# @param id optional SSO session ID
# @return nothing
sub updatePersistentSession {
my ( $self, $req, $infos, $uid, $id ) = @_;
# Return if no infos to update
2019-07-02 20:03:40 +02:00
return ()
unless ( ref $infos eq 'HASH'
and %$infos
and !$self->conf->{disablePersistentStorage} );
2017-02-07 13:52:56 +01:00
$uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} }
2019-02-28 17:40:15 +01:00
|| $req->userData->{ $self->conf->{whatToTrace} };
$self->logger->debug("Found 'whatToTrace' -> $uid");
2017-02-07 13:52:56 +01:00
unless ($uid) {
2019-02-28 17:40:15 +01:00
$self->logger->debug('No uid found, skipping updatePersistentSession');
2017-02-07 13:52:56 +01:00
return ();
}
2017-02-15 07:41:50 +01:00
$self->logger->debug("Update $uid persistent session");
2016-06-01 19:36:51 +02:00
2017-02-19 08:17:48 +01:00
# Update current session
$self->updateSession( $req, $infos, $id );
my $persistentSession = $self->getPersistentSession( $uid, $infos );
2016-06-01 19:36:51 +02:00
if ( $persistentSession->error ) {
2017-02-15 07:41:50 +01:00
$self->logger->error(
"Cannot update persistent session " . getPSessionID($uid) );
2017-02-15 07:41:50 +01:00
$self->logger->error( $persistentSession->error );
2016-06-01 19:36:51 +02:00
}
}
# Update session stored.
# If no id is given, try to get it from cookie.
# If the session is available, update data with $info.
2016-06-01 19:36:51 +02:00
# Note that outdated session data may remain some time on
# server local cache, if there are several LL::NG servers.
# @param infos hash reference of information to update
# @param id Session ID
# @return nothing
sub updateSession {
my ( $self, $req, $infos, $id ) = @_;
# Return if no infos to update
return () unless ( ref $infos eq 'HASH' and %$infos );
# Recover session ID unless given
2019-12-31 17:14:44 +01:00
$id ||= $req->id || $req->userData->{_session_id};
2016-06-01 19:36:51 +02:00
if ($id) {
# Update sessionInfo data
2021-07-28 10:38:45 +02:00
## sessionInfo updated if $id defined : quite strange!!
2017-11-11 14:06:23 +01:00
## See https://gitlab.ow2.org/lemonldap-ng/lemonldap-ng/issues/430
2020-04-06 23:28:01 +02:00
$self->logger->debug("Update session $id");
2016-06-01 19:36:51 +02:00
foreach ( keys %$infos ) {
2019-02-28 16:51:16 +01:00
$self->logger->debug("Update sessionInfo $_");
$self->_dump( $infos->{$_} );
2021-06-25 07:54:03 +02:00
$req->{sessionInfo}->{$_} = $infos->{$_};
2021-08-27 14:35:07 +02:00
if ( $self->HANDLER->data->{_session_id}
&& $id eq $self->HANDLER->data->{_session_id} )
{
2021-06-25 07:54:03 +02:00
$self->HANDLER->data->{$_} = $infos->{$_};
}
2016-06-01 19:36:51 +02:00
}
# Update session in global storage with _updateTime
$infos->{_updateTime} = strftime( "%Y%m%d%H%M%S", localtime() );
2019-02-28 17:40:15 +01:00
if ( my $apacheSession =
$self->getApacheSession( $id, info => $infos ) )
{
2016-06-01 19:36:51 +02:00
if ( $apacheSession->error ) {
2017-02-15 07:41:50 +01:00
$self->logger->error("Cannot update session $id");
$self->logger->error( $apacheSession->error );
2016-06-01 19:36:51 +02:00
}
}
}
}
2016-04-14 21:49:27 +02:00
# Delete an existing session. If "securedCookie" is set to 2, the http session
# will also be removed.
# @param h tied Apache::Session object
# @param preserveCookie do not delete cookie
# @return True if session has been deleted
sub _deleteSession {
my ( $self, $req, $session, $preserveCookie ) = @_;
# Invalidate http cookie and session, if set
2016-07-12 20:58:33 +02:00
if ( $self->conf->{securedCookie} >= 2 ) {
2016-04-14 21:49:27 +02:00
# Try to find a linked http session (securedCookie == 2)
2016-07-12 20:58:33 +02:00
if ( $self->conf->{securedCookie} == 2
and my $id2 = $session->data->{_httpSession} )
{
2017-01-24 06:10:57 +01:00
if ( my $session2 = $self->getApacheSession($id2) ) {
2016-04-14 21:49:27 +02:00
$session2->remove;
if ( $session2->error ) {
2017-02-15 07:41:50 +01:00
$self->logger->debug(
"Unable to remove linked session $id2");
$self->logger->debug( $session2->error );
2016-04-14 21:49:27 +02:00
}
}
}
# Create an obsolete cookie to remove it
2017-01-13 15:35:02 +01:00
$req->addCookie(
$self->cookie(
name => $self->conf->{cookieName} . 'http',
value => 0,
domain => $self->conf->{domain},
secure => 0,
2017-03-15 11:01:29 +01:00
expires => 'Wed, 21 Oct 2015 00:00:00 GMT'
2017-01-13 15:35:02 +01:00
)
) unless ($preserveCookie);
2016-04-14 21:49:27 +02:00
}
HANDLER->localUnlog( $req, $session->id );
2016-04-14 21:49:27 +02:00
$session->remove;
# Create an obsolete cookie to remove it
2017-01-13 15:35:02 +01:00
$req->addCookie(
$self->cookie(
name => $self->conf->{cookieName},
value => 0,
domain => $self->conf->{domain},
secure => $self->conf->{securedCookie},
2017-03-15 11:01:29 +01:00
expires => 'Wed, 21 Oct 2015 00:00:00 GMT'
2017-01-13 15:35:02 +01:00
)
) unless ($preserveCookie);
2016-04-14 21:49:27 +02:00
# Log
my $user = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
my $mod = $req->{sessionInfo}->{_auth};
$self->userLogger->notice(
"User $user has been disconnected from $mod ($req->{sessionInfo}->{ipAddr})"
) if $user;
2016-04-14 21:49:27 +02:00
return $session->error ? 0 : 1;
}
2016-04-03 10:44:58 +02:00
# Check if an URL's domain name is declared in LL::NG config or is declared as
# trusted domain
sub isTrustedUrl {
my ( $self, $url ) = @_;
2016-05-23 13:53:09 +02:00
return $url =~ $self->trustedDomainsRe ? 1 : 0;
2016-04-03 10:44:58 +02:00
}
2016-11-16 16:27:01 +01:00
sub stamp {
my $self = shift;
2019-02-28 17:40:15 +01:00
my $res =
$self->conf->{cipher}
? $self->conf->{cipher}->encrypt( time() )
: 1;
$res =~ s/\+/%2B/g;
return $res;
2016-11-16 16:27:01 +01:00
}
2016-11-22 13:34:09 +01:00
# Transfer POST data with auto submit
# @return void
sub autoPost {
my ( $self, $req ) = @_;
# Get URL and Form fields
$req->{urldc} = $req->postUrl;
my $formFields = $req->postFields;
$self->clearHiddenFormValue($req);
foreach ( keys %$formFields ) {
$self->setHiddenFormValue( $req, $_, $formFields->{$_}, "", 0 );
}
# Display info before redirecting
if ( $req->info() ) {
$req->data->{infoFormMethod} = $req->param('method') || "post";
2016-11-22 13:34:09 +01:00
return PE_INFO;
}
$req->data->{redirectFormMethod} = "post";
2016-11-22 13:34:09 +01:00
return PE_REDIRECT;
}
# Add element into $self->{portalHiddenFormValues}, those values could be
# used to hide values into HTML form.
# @param fieldname The field name which will contain the corresponding value
2016-11-22 13:34:09 +01:00
# @param value The associated value
# @param prefix Prefix of the field key
# @param base64 Encode value in base64
# @return nothing
sub setHiddenFormValue {
my ( $self, $req, $key, $val, $prefix, $base64 ) = @_;
# Default values
$prefix = "lmhidden_" unless defined $prefix;
$base64 = 1 unless defined $base64;
2017-07-11 13:50:21 +02:00
$val = '' unless defined $val;
2016-11-22 13:34:09 +01:00
# Store value
if ( defined $val or !( $val & ~$val ) ) {
2016-12-02 06:47:38 +01:00
$key = $prefix . $key;
2018-03-10 09:33:14 +01:00
2019-04-03 17:54:58 +02:00
$val = encode_base64($val) if $base64;
2016-11-22 13:34:09 +01:00
$req->{portalHiddenFormValues}->{$key} = $val;
2017-02-15 07:41:50 +01:00
$self->logger->debug("Store $val in hidden key $key");
2016-11-22 13:34:09 +01:00
}
}
## @method public void getHiddenFormValue(string fieldname, string prefix, boolean base64)
# Get value into $self->{portalHiddenFormValues}.
# @param fieldname The existing field name which contains a value
# @param prefix Prefix of the field key
# @param base64 Decode value from base64
# @return string The associated value
sub getHiddenFormValue {
my ( $self, $req, $key, $prefix, $base64 ) = @_;
# Default values
$prefix = "lmhidden_" unless defined $prefix;
$base64 = 1 unless defined $base64;
$key = $prefix . $key;
# Get value
my $val = $req->param($key);
if ( defined $val ) {
2016-11-22 13:34:09 +01:00
$val = decode_base64($val) if $base64;
2017-02-15 07:41:50 +01:00
$self->logger->debug("Hidden value $val found for key $key");
return $val;
2016-11-22 13:34:09 +01:00
}
# No value found
return undef;
}
## @method protected void clearHiddenFormValue(arrayref keys)
# Clear values form stored hidden fields
# Delete all keys if no keys provided
# @param keys Array reference of keys
# @return nothing
sub clearHiddenFormValue {
my ( $self, $req, $keys ) = @_;
unless ( defined $keys ) {
delete $req->{portalHiddenFormValues};
2017-02-15 07:41:50 +01:00
$self->logger->debug("Delete all hidden values");
2016-11-22 13:34:09 +01:00
}
else {
foreach (@$keys) {
delete $req->{portalHiddenFormValues}->{$_};
2017-02-15 07:41:50 +01:00
$self->logger->debug("Delete hidden value for key $_");
2016-11-22 13:34:09 +01:00
}
}
return;
}
2016-11-28 22:15:57 +01:00
# Get the first value of a multivaluated session value
sub getFirstValue {
my ( $self, $value ) = @_;
my @values = split /$self->{conf}->{multiValuesSeparator}/, $value;
return $values[0];
}
2016-12-15 22:22:15 +01:00
sub info {
my ( $self, $req, $info ) = @_;
return $req->info($info);
2016-12-15 22:22:15 +01:00
}
2016-12-22 09:40:50 +01:00
sub fullUrl {
my ( $self, $req ) = @_;
my $pHost = $self->conf->{portal};
$pHost =~ s#^(https?://[^/]+)(?:/.*)?$#$1#;
2018-06-26 21:45:55 +02:00
return $pHost . $req->env->{REQUEST_URI};
2016-12-22 09:40:50 +01:00
}
2017-01-13 15:35:02 +01:00
sub cookie {
my ( $self, %h ) = @_;
my @res;
$res[0] = "$h{name}" or die("name required");
$res[0] .= "=$h{value}";
$h{path} ||= '/';
$h{HttpOnly} //= $self->conf->{httpOnly};
2017-03-15 11:01:29 +01:00
$h{max_age} //= $self->conf->{cookieExpiration}
2019-02-28 17:40:15 +01:00
if ( $self->conf->{cookieExpiration} );
$h{SameSite} ||= $self->cookieSameSite;
2020-01-20 06:21:09 +01:00
foreach (qw(domain path expires max_age HttpOnly SameSite)) {
2017-01-13 15:35:02 +01:00
my $f = $_;
$f =~ s/_/-/g;
push @res, "$f=$h{$_}" if ( $h{$_} );
}
2017-01-17 21:38:22 +01:00
push @res, 'secure' if ( $h{secure} );
2017-01-13 15:35:02 +01:00
return join( '; ', @res );
}
sub _dump {
my ( $self, $variable ) = @_;
2019-08-27 10:10:11 +02:00
if ( $self->conf->{logLevel} eq 'debug' ) {
require Data::Dumper;
$Data::Dumper::Indent = 0;
$Data::Dumper::Useperl = 1;
$self->logger->debug( "Dump: " . Data::Dumper::Dumper($variable) );
}
2017-01-13 15:35:02 +01:00
return;
}
# Warning: this function returns a JSON string
sub getTrOver {
my ( $self, $req, $templateDir ) = @_;
my $templateDir //= $self->conf->{templateDir} . '/' . $self->getSkin($req);
unless ( $self->trOverCache->{$templateDir} ) {
2019-02-21 16:25:02 +01:00
# Override messages
my $trOverMessages = JSON::from_json( $self->trOver );
opendir( DIR, $templateDir );
my @langfiles = grep( /\.json$/, readdir(DIR) );
close(DIR);
foreach my $file (@langfiles) {
my ($lang) = ( $file =~ /^(\w+)\.json/ );
$self->logger->debug("Use $file to override messages");
if ( open my $json, "<", $templateDir . "/" . $file ) {
local $/ = undef;
$trOverMessages->{$lang} = JSON::from_json(<$json>);
}
else {
$self->logger->error("Unable to read $file");
}
2019-02-21 16:25:02 +01:00
}
$self->trOverCache->{$templateDir} = JSON::to_json($trOverMessages);
}
return $self->trOverCache->{$templateDir};
}
sub sendHtml {
my ( $self, $req, $template, %args ) = @_;
my $templateDir = $self->conf->{templateDir} . '/' . $self->getSkin($req);
$self->templateDir( [ $templateDir, @{ $self->templateDir } ] );
# Check template
$args{templateDir} = $templateDir;
my $tmpl = $args{templateDir} . "/$template.tpl";
unless ( -f $tmpl ) {
$self->logger->debug("Template $tmpl not found");
$args{templateDir} = $self->conf->{templateDir} . '/bootstrap';
$tmpl = $args{templateDir} . "/$template.tpl";
$self->logger->debug("-> Trying to load $tmpl");
}
$args{params}->{TROVER} = $self->getTrOver( $req, $templateDir );
2017-03-15 23:27:58 +01:00
my $res = $self->SUPER::sendHtml( $req, $template, %args );
push @{ $res->[1] },
2019-02-28 17:40:15 +01:00
'X-XSS-Protection' => '1; mode=block',
'X-Content-Type-Options' => 'nosniff',
'Cache-Control' => 'no-cache, no-store, must-revalidate', # HTTP 1.1
'Pragma' => 'no-cache', # HTTP 1.0
'Expires' => '0'; # Proxies
2017-01-21 10:17:24 +01:00
$self->setCorsHeaderFromConfig($res);
2019-05-31 17:00:39 +02:00
# Set authorized URL for POST
2018-11-26 14:40:21 +01:00
my $csp = $self->csp . "form-action " . $self->conf->{cspFormAction};
2017-01-24 06:10:57 +01:00
if ( my $url = $req->urldc ) {
2018-08-15 22:47:23 +02:00
$self->logger->debug("Required urldc : $url");
2021-03-02 08:46:59 +01:00
$url =~ URIRE;
$url = $2 . '://' . $3 . ( $4 ? ":$4" : '' );
$self->logger->debug("Set CSP form-action with urldc : $url");
2018-08-15 22:47:23 +02:00
$csp .= " $url";
2017-01-21 10:50:59 +01:00
}
2018-08-08 10:09:33 +02:00
my $url = $args{params}->{URL};
2018-08-15 22:47:23 +02:00
if ( defined $url ) {
$self->logger->debug("Required Params URL : $url");
2021-03-02 08:46:59 +01:00
if ( $url =~ URIRE ) {
$url = $2 . '://' . $3 . ( $4 ? ":$4" : '' );
2019-02-28 17:40:15 +01:00
$self->logger->debug("Set CSP form-action with Params URL : $url");
2018-08-15 22:47:23 +02:00
$csp .= " $url";
}
2017-01-21 10:17:24 +01:00
}
2021-04-22 17:48:18 +02:00
if ( defined $req->data->{cspFormAction}
and ref( $req->data->{cspFormAction} ) eq "HASH" )
{
my $request_csp_form_action =
join( " ", keys %{ $req->data->{cspFormAction} } );
$self->logger->debug( "Set CSP form-action with request URL: "
. $request_csp_form_action );
$csp .= " " . $request_csp_form_action;
}
# Set SAML Discovery Protocol in form-action
# See https://github.com/w3c/webappsec-csp/issues/8
if ( $self->conf->{samlDiscoveryProtocolActivation}
and defined $self->conf->{samlDiscoveryProtocolURL} )
{
$self->logger->debug(
"Add SAML Discovery Protocol URL in CSP form-action");
$csp .= " " . $self->conf->{samlDiscoveryProtocolURL};
}
2018-08-08 10:19:16 +02:00
$csp .= ';';
2017-01-21 10:17:24 +01:00
# Deny using portal in frame except if it is required
unless ( $req->frame
or $self->conf->{portalAntiFrame} == 0
or $self->conf->{cspFrameAncestors} )
{
2017-03-15 23:27:58 +01:00
push @{ $res->[1] }, 'X-Frame-Options' => 'DENY';
2017-01-21 10:17:24 +01:00
$csp .= "frame-ancestors 'none';";
}
if ( $self->conf->{cspFrameAncestors} ) {
push @{ $res->[1] }, 'X-Frame-Options' => 'ALLOW-FROM '
. "$self->{conf}->{cspFrameAncestors};";
$csp .= "frame-ancestors $self->{conf}->{cspFrameAncestors};";
}
2017-01-21 10:17:24 +01:00
# Check if frames need to be embedded
# FIXME: we should use $req->data->{cspChildSrc} anywhere an iframe is
# created in the code, and remove this
2017-01-21 10:17:24 +01:00
my @url;
if ( $req->info ) {
@url = map { s#https?://([^/]+).*#$1#; $_ }
2019-02-28 17:40:15 +01:00
( $req->info =~ /<iframe.*?src="(.*?)"/sg );
2017-01-21 10:17:24 +01:00
}
# Update child-src header from request data
if ( ref( $req->data->{cspChildSrc} ) eq "HASH" ) {
push @url, keys %{ $req->data->{cspChildSrc} };
}
2017-01-21 10:17:24 +01:00
if (@url) {
$csp .= join( ' ', 'child-src', @url, "'self'" ) . ';';
}
2017-01-21 10:17:24 +01:00
# Set CSP header
2017-03-15 23:27:58 +01:00
push @{ $res->[1] }, 'Content-Security-Policy' => $csp;
2018-08-08 10:13:58 +02:00
$self->logger->debug("Apply following CSP : $csp");
2017-03-15 23:27:58 +01:00
return $res;
}
2017-03-16 12:38:52 +01:00
sub sendCss {
my ( $self, $req ) = @_;
my $s = '/* LL::NG Portal CSS */';
if ( $self->conf->{portalSkinBackground} ) {
2019-02-28 17:40:15 +01:00
$s .=
'html,body{background:url("'
. $self->staticPrefix
. '/common/backgrounds/'
. $self->conf->{portalSkinBackground}
. '") no-repeat center fixed;'
. 'background-size:cover;}';
}
2017-03-16 12:38:52 +01:00
return [
200,
2019-02-28 17:40:15 +01:00
[
'Content-Type' => 'text/css',
2017-03-16 12:38:52 +01:00
'Content-Length' => length($s),
'Cache-Control' => 'public,max-age=3600',
],
[$s]
];
}
sub lmError {
2019-02-28 21:59:59 +01:00
my ( $self, $req, $error ) = @_;
my $httpError = $req->param('code') || $error;
# Check URL
$self->controlUrl($req);
$req->pdata( {} ) unless ( $httpError == 404 );
if ( $req->wantJSON ) {
return $self->sendJSONresponse(
$req,
{ error => $httpError, result => 0 },
code => $httpError
);
}
my %templateParams = (
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
LOGOUT_URL => $self->conf->{portal} . "?logout=1",
URL => $req->{urldc},
);
# Error code
$templateParams{"ERROR$_"} = ( $httpError == $_ ? 1 : 0 )
2019-02-28 17:40:15 +01:00
foreach ( 403, 404, 500, 502, 503 );
return $self->sendHtml( $req, 'error', params => \%templateParams );
}
sub rebuildCookies {
my ( $self, $req ) = @_;
my @tmp;
2019-02-28 17:40:15 +01:00
for ( my $i = 0 ; $i < @{ $req->{respHeaders} } ; $i += 2 ) {
2019-04-10 07:14:36 +02:00
push @tmp, $req->respHeaders->[$i], $req->respHeaders->[ $i + 1 ]
unless ( $req->respHeaders->[$i] eq 'Set-Cookie' );
}
$req->{respHeaders} = \@tmp;
$self->buildCookie($req);
}
sub tplParams {
my ( $self, $req ) = @_;
my %templateParams;
my $portalPath = $self->conf->{portal};
2017-04-10 20:45:45 +02:00
$portalPath =~ s#^https?://[^/]+/?#/#;
$portalPath =~ s#[^/]+\.fcgi$##;
for my $session_key ( keys %{ $req->{sessionInfo} } ) {
2019-02-28 17:40:15 +01:00
$templateParams{ "session_" . $session_key } =
$req->{sessionInfo}->{$session_key};
}
for my $env_key ( keys %{ $req->env } ) {
$templateParams{ "env_" . $env_key } = $req->env->{$env_key};
}
return (
PORTAL_URL => $self->conf->{portal},
2022-06-18 17:54:07 +02:00
MAIN_LOGO => $self->conf->{portalMainLogo},
LANGS => $self->conf->{showLanguages},
SCROLL_TOP => $self->conf->{scrollTop},
SKIN => $self->getSkin($req),
2017-04-10 20:45:45 +02:00
SKIN_PATH => $portalPath . "skins",
2021-09-23 17:36:36 +02:00
SAMESITE => getSameSite( $self->conf ),
SKIN_BG => $self->conf->{portalSkinBackground},
2022-05-16 22:32:11 +02:00
FAVICON => $self->conf->{portalFavicon} || 'common/favicon.ico',
CUSTOM_CSS => $self->conf->{portalCustomCss},
(
$self->customParameters
? ( %{ $self->customParameters } )
: ()
),
%templateParams
);
}
2017-02-19 08:17:48 +01:00
sub registerLogin {
my ( $self, $req ) = @_;
return
2019-02-28 17:40:15 +01:00
unless ( $self->conf->{loginHistoryEnabled}
and defined $req->authResult );
2019-02-28 17:40:15 +01:00
# Check old login history
if ( $req->sessionInfo->{loginHistory} ) {
if ( !$req->sessionInfo->{_loginHistory} ) {
$self->logger->debug("Restore old login history");
# Restore success login
$req->sessionInfo->{_loginHistory}->{successLogin} =
$req->sessionInfo->{loginHistory}->{successLogin};
# Restore failed login, with generic error
if ( $req->sessionInfo->{loginHistory}->{failedLogin} ) {
$self->logger->debug("Restore old failed logins");
$req->sessionInfo->{_loginHistory}->{failedLogin} = [];
foreach (
@{ $req->sessionInfo->{loginHistory}->{failedLogin} } )
{
$self->logger->debug(
"Replace old failed login error " . $_->{error} );
$_->{error} = 5;
push @{ $req->sessionInfo->{_loginHistory}->{failedLogin} },
$_;
}
}
}
$self->updatePersistentSession( $req, { 'loginHistory' => undef } );
2019-02-28 17:40:15 +01:00
delete $req->sessionInfo->{loginHistory};
}
2017-03-21 22:00:37 +01:00
my $history = $req->sessionInfo->{_loginHistory} ||= {};
2021-08-27 14:35:07 +02:00
my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login';
2017-02-19 08:17:48 +01:00
$history->{$type} ||= [];
$self->logger->debug("Current login saved into $type");
# Gather current login's parameters
my $login = $self->_sumUpSession( $req->{sessionInfo}, 1 );
$login->{error} = $self->error( $req->authResult )
2019-02-28 17:40:15 +01:00
if ( $req->authResult );
2017-02-19 08:17:48 +01:00
2018-10-12 10:04:03 +02:00
$self->logger->debug( " Current login -> " . $login->{error} )
2019-02-28 17:40:15 +01:00
if ( $login->{error} );
2018-09-22 10:14:29 +02:00
2017-02-19 08:17:48 +01:00
# Add current login into history
unshift @{ $history->{$type} }, $login;
# Forget oldest logins
splice @{ $history->{$type} }, $self->conf->{ $type . "Number" }
2019-02-28 17:40:15 +01:00
if ( scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
2017-02-19 08:17:48 +01:00
# Save into persistent session
$self->updatePersistentSession( $req, { _loginHistory => $history, } );
2018-11-26 14:40:21 +01:00
PE_OK;
2017-02-19 08:17:48 +01:00
}
# put main session data into a hash ref
# @param hashref $session The session to sum up
# @return hashref
sub _sumUpSession {
my ( $self, $session, $withoutUser ) = @_;
2019-02-28 17:40:15 +01:00
my $res =
$withoutUser
? {}
: { user => $session->{ $self->conf->{whatToTrace} } };
$res->{$_} = $session->{$_} foreach (
"_utime", "ipAddr",
keys %{ $self->conf->{sessionDataToRemember} },
keys %{ $self->pluginSessionDataToRemember }
);
2017-02-19 08:17:48 +01:00
return $res;
}
2019-09-16 12:11:50 +02:00
sub corsPreflight {
my ( $self, $req ) = @_;
my @headers;
my $res = [ 204, \@headers, [] ];
$self->setCorsHeaderFromConfig($res);
return $res;
2019-09-16 12:11:50 +02:00
}
sub sendJSONresponse {
my ( $self, $req, $j, %args ) = @_;
my $res = Lemonldap::NG::Common::PSGI::sendJSONresponse(@_);
# Handle caching
if ( $args{ttl} and $args{ttl} =~ /^\d+$/ ) {
push @{ $res->[1] }, 'Cache-Control' => 'public, max-age=' . $args{ttl};
}
else {
push @{ $res->[1] },
'Cache-Control' => 'no-cache, no-store, must-revalidate',
'Pragma' => 'no-cache',
'Expires' => '0';
}
# If this is a cross-domain request from the portal itself
# (Ajax SSL to a different VHost)
# we allow CORS
if ( $req->origin
and index( $self->conf->{portal}, $req->origin ) == 0 )
{
$self->logger->debug('AJAX request from portal, allowing CORS');
push @{ $res->[1] },
"Access-Control-Allow-Origin" => $req->origin,
"Access-Control-Allow-Methods" => "*",
"Access-Control-Allow-Credentials" => "true";
}
else {
$self->setCorsHeaderFromConfig($res);
}
return $res;
}
sub sendRawHtml {
my ($self) = $_[0];
my $res = Lemonldap::NG::Common::PSGI::sendRawHtml(@_);
$self->setCorsHeaderFromConfig($res);
return $res;
}
sub setCorsHeaderFromConfig {
my ( $self, $response ) = @_;
if ( $self->conf->{corsEnabled} ) {
my @cors = split /;/, $self->cors;
push @{ $response->[1] }, @cors;
$self->logger->debug('Apply following CORS policy :');
$self->logger->debug(" $_") for @cors;
}
}
# Temlate loader
sub loadTemplate {
my ( $self, $req, $name, %prm ) = @_;
$name .= '.tpl';
my $tpl = HTML::Template->new(
filename => $name,
path => [
$self->conf->{templateDir} . '/' . $self->getSkin($req),
$self->conf->{templateDir} . '/bootstrap/',
$self->conf->{templateDir} . '/common/'
],
search_path_on_include => 1,
die_on_bad_params => 0,
die_on_missing_include => 1,
cache => ( defined $prm{cache} ? $prm{cache} : 1 ),
global_vars => 0,
( $prm{filter} ? ( filter => $prm{filter} ) : () ),
);
if ( $prm{params} ) {
$tpl->param( %{ $prm{params} } );
}
return $tpl->output;
}
# This method extracts the scheme://host:port part of a URL for use in
# Content-Security-Polity header
sub cspGetHost {
my ( $self, $url ) = @_;
my $uri = $url // "";
unless ( $uri->isa("URI") ) {
$uri = URI->new($uri);
}
return (
$uri->scheme . "://" . ( $uri->_port ? $uri->host_port : $uri->host ) );
}
2021-08-27 14:35:07 +02:00
sub buildUrl {
my $self = shift;
return $self->portal unless @_;
# URL base is $self->portal unless first arg is an URL
my $uri =
URI->new( ( $_[0] =~ m#^https?://# ) ? shift(@_) : $self->portal );
my @pathSg = grep { $_ ne '' } $uri->path_segments;
while (@_) {
my $s = shift;
if ( ref $s ) {
$uri->query_form($s);
if (@_) {
require Carp;
Carp::confess('Query must be the last arg of buildUrl');
}
}
else {
push @pathSg, $s;
}
}
$uri->path_segments(@pathSg);
return $uri;
}
2016-03-29 23:09:55 +02:00
1;