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;
|
|
|
|
|
2016-04-07 23:31:56 +02:00
|
|
|
our $VERSION = '2.0.0';
|
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;
|
2016-03-30 21:51:12 +02:00
|
|
|
|
2016-04-03 08:33:50 +02:00
|
|
|
# List constants
|
|
|
|
sub authProcess { qw(extractFormInfo getUser authenticate) }
|
|
|
|
|
|
|
|
sub sessionDatas {
|
2016-12-01 23:25:05 +01:00
|
|
|
qw(setAuthSessionInfo setSessionInfo setMacros setGroups setPersistentSessionInfo
|
2016-04-03 08:33:50 +02:00
|
|
|
setLocalGroups store buildCookie);
|
|
|
|
}
|
|
|
|
|
|
|
|
# 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 ) = @_;
|
2016-03-30 21:51:15 +02:00
|
|
|
bless $req, 'Lemonldap::NG::Portal::Main::Request';
|
2016-05-31 13:47:10 +02:00
|
|
|
$req->init();
|
2016-04-03 18:51:23 +02:00
|
|
|
return $self->Lemonldap::NG::Common::PSGI::Router::handler($req);
|
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 ) = @_;
|
|
|
|
return $self->sendJSONresponse( $req, { status => 1 } );
|
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2016-03-31 22:08:43 +02:00
|
|
|
[
|
2016-07-14 10:25:05 +02:00
|
|
|
'controlUrl', @{ $self->beforeAuth },
|
|
|
|
$self->authProcess, @{ $self->betweenAuthAndDatas },
|
|
|
|
$self->sessionDatas, @{ $self->afterDatas },
|
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,
|
2016-03-31 22:08:43 +02:00
|
|
|
[
|
2016-04-03 08:33:50 +02:00
|
|
|
'restoreArgs', 'controlUrl',
|
2016-07-14 10:25:05 +02:00
|
|
|
@{ $self->beforeAuth }, $self->authProcess,
|
|
|
|
@{ $self->betweenAuthAndDatas }, $self->sessionDatas,
|
2016-04-04 07:08:26 +02:00
|
|
|
@{ $self->afterDatas },
|
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 ) = @_;
|
2016-04-14 21:49:27 +02:00
|
|
|
return $self->do(
|
|
|
|
$req,
|
|
|
|
[
|
2016-05-23 18:55:23 +02:00
|
|
|
'importHandlerDatas', 'controlUrl',
|
|
|
|
'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,
|
|
|
|
[
|
2016-05-23 18:55:23 +02:00
|
|
|
'importHandlerDatas', 'restoreArgs',
|
|
|
|
'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
|
|
|
}
|
|
|
|
|
2017-02-16 17:11:12 +01:00
|
|
|
sub refresh {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
$req->mustRedirect(1);
|
|
|
|
my %datas = %{ $req->userData };
|
|
|
|
$req->user( $datas{ $self->conf->{whatToTrace} } );
|
|
|
|
$req->id( $datas{_session_id} );
|
2017-02-17 08:40:15 +01:00
|
|
|
$self->userLogger->notice( 'Refresh request for ' . $req->user );
|
2017-02-16 17:11:12 +01:00
|
|
|
foreach ( keys %datas ) {
|
|
|
|
delete $datas{$_} unless ( /^_/ or /^(?:startTime)$/ );
|
|
|
|
}
|
|
|
|
return $self->do(
|
|
|
|
$req,
|
|
|
|
[
|
|
|
|
'getUser',
|
|
|
|
@{ $self->betweenAuthAndDatas },
|
|
|
|
'setAuthSessionInfo',
|
|
|
|
'setSessionInfo',
|
|
|
|
'setMacros',
|
|
|
|
'setGroups',
|
|
|
|
'setLocalGroups',
|
|
|
|
sub {
|
|
|
|
$req->sessionInfo->{$_} = $datas{$_} foreach ( keys %datas );
|
|
|
|
return PE_OK;
|
|
|
|
},
|
|
|
|
'store',
|
|
|
|
]
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
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,
|
|
|
|
[
|
|
|
|
'controlUrl', @{ $self->beforeLogout },
|
|
|
|
'authLogout', 'deleteSession'
|
|
|
|
]
|
|
|
|
);
|
2016-04-18 22:23:40 +02:00
|
|
|
}
|
|
|
|
|
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);
|
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} ) {
|
|
|
|
print $p ( $req->user ? $req->user : $req->address ) . ' => '
|
|
|
|
. $req->uri
|
|
|
|
. " $err\n";
|
|
|
|
}
|
2017-02-19 08:17:48 +01:00
|
|
|
|
|
|
|
# Update history
|
|
|
|
if ( $self->conf->{loginHistoryEnabled} ) {
|
|
|
|
$self->registerLogin($req);
|
|
|
|
}
|
2016-12-18 09:07:48 +01:00
|
|
|
if ( $err == PE_SENDRESPONSE ) {
|
|
|
|
return $req->response;
|
|
|
|
}
|
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 ) {
|
2016-04-01 07:24:27 +02:00
|
|
|
return [
|
|
|
|
401,
|
|
|
|
[
|
|
|
|
'WWW-Authenticate' => "SSO " . $self->conf->{portal},
|
|
|
|
'Access-Control-Allow-Origin' => '*'
|
|
|
|
],
|
2017-02-06 00:04:28 +01:00
|
|
|
[qq'{"result":0,"error":$err"}']
|
2016-04-01 07:24:27 +02:00
|
|
|
];
|
2016-03-31 22:08:43 +02:00
|
|
|
}
|
2016-07-11 23:02:32 +02:00
|
|
|
elsif ( $err > 0 ) {
|
|
|
|
return $self->sendJSONresponse(
|
|
|
|
$req,
|
|
|
|
{ result => 0, error => $err },
|
|
|
|
code => 400
|
|
|
|
);
|
|
|
|
}
|
2016-03-31 22:08:43 +02:00
|
|
|
else {
|
2016-07-11 23:02:32 +02:00
|
|
|
return $self->sendJSONresponse(
|
|
|
|
$req,
|
|
|
|
{
|
|
|
|
result => 1,
|
|
|
|
code => $err
|
|
|
|
}
|
|
|
|
);
|
2016-03-31 22:08:43 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
2016-11-22 21:55:10 +01:00
|
|
|
if (
|
|
|
|
$err
|
|
|
|
and $err != PE_LOGOUT_OK
|
|
|
|
and (
|
|
|
|
$err != PE_REDIRECT
|
|
|
|
or ( $err == PE_REDIRECT
|
2016-12-02 06:47:38 +01:00
|
|
|
and $req->datas->{redirectFormMethod}
|
2016-11-22 21:55:10 +01:00
|
|
|
and $req->datas->{redirectFormMethod} eq 'post' )
|
2016-12-22 09:40:50 +01:00
|
|
|
or ( $err == PE_REDIRECT and $req->info )
|
2016-11-22 21:55:10 +01:00
|
|
|
)
|
|
|
|
)
|
|
|
|
{
|
2016-04-11 07:12:39 +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-04-11 07:12:39 +02:00
|
|
|
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 $mod = {
|
|
|
|
auth => '_authentication',
|
|
|
|
user => '_userDB',
|
|
|
|
password => '_passwordDB'
|
|
|
|
}->{$type}
|
|
|
|
)
|
|
|
|
{
|
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 );
|
2016-07-13 07:10:57 +02: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
|
2016-05-23 18:55:18 +02:00
|
|
|
$req->{urldc} ||= $self->conf->{portal} if ( $req->mustRedirect );
|
2016-04-03 10:44:58 +02:00
|
|
|
|
|
|
|
# Redirection should be made if urldc defined
|
2016-05-26 23:26:47 +02:00
|
|
|
if ( $req->{urldc} and not $req->param('lmError') ) {
|
|
|
|
if ( $self->_jsRedirect->() ) {
|
|
|
|
$req->error(PE_REDIRECT);
|
|
|
|
$req->datas->{redirectFormMethod} = "get";
|
|
|
|
}
|
|
|
|
else {
|
2017-02-15 07:41:50 +01:00
|
|
|
return [ 302,
|
|
|
|
[ Location => $req->{urldc}, @{ $req->respHeaders } ], [] ];
|
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
|
|
|
}
|
|
|
|
|
2016-04-04 22:39:22 +02:00
|
|
|
# Try to recover the session corresponding to id and return session datas.
|
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-04-04 22:39:22 +02:00
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2016-04-04 22:39:22 +02:00
|
|
|
my $as = Lemonldap::NG::Common::Session->new(
|
|
|
|
{
|
|
|
|
storageModule => $self->conf->{globalStorage},
|
|
|
|
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},
|
2017-02-20 22:00:05 +01:00
|
|
|
( $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 ) {
|
2017-02-20 22:00:05 +01:00
|
|
|
$self->lmLog(
|
|
|
|
$err,
|
|
|
|
(
|
|
|
|
$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;
|
|
|
|
}
|
|
|
|
|
2016-06-09 13:45:10 +02:00
|
|
|
my $now = time;
|
|
|
|
if (
|
2016-07-22 11:47:50 +02:00
|
|
|
$id
|
|
|
|
and defined $as->data->{_utime}
|
2016-06-09 13:45:10 +02:00
|
|
|
and (
|
|
|
|
$now - $as->data->{_utime} > $self->conf->{timeout}
|
|
|
|
or ( $self->conf->{timeoutActivity}
|
|
|
|
and $as->data->{_lastSeen}
|
|
|
|
and $now - $as->data->{_lastSeen} >
|
|
|
|
$self->conf->{timeoutActivity} )
|
|
|
|
)
|
|
|
|
)
|
|
|
|
{
|
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 datas.
|
|
|
|
sub getPersistentSession {
|
2017-02-20 22:00:05 +01:00
|
|
|
my ( $self, $uid, $info ) = @_;
|
2016-04-04 22:39:22 +02:00
|
|
|
|
|
|
|
return unless defined $uid;
|
|
|
|
|
|
|
|
# Compute persistent identifier
|
|
|
|
my $pid = $self->_md5hash($uid);
|
|
|
|
|
2017-02-27 21:48:00 +01:00
|
|
|
$info->{_session_uid} = $uid;
|
|
|
|
|
2016-04-04 22:39:22 +02:00
|
|
|
my $ps = Lemonldap::NG::Common::Session->new(
|
|
|
|
{
|
2016-04-05 22:46:11 +02:00
|
|
|
storageModule => $self->conf->{persistentStorage},
|
|
|
|
storageModuleOptions => $self->conf->{persistentStorageOptions},
|
2016-04-04 22:39:22 +02:00
|
|
|
id => $pid,
|
|
|
|
force => 1,
|
|
|
|
kind => "Persistent",
|
2017-02-20 22:00:05 +01:00
|
|
|
( $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
|
|
|
}
|
|
|
|
|
|
|
|
# Set _session_uid if not already present
|
|
|
|
unless ( defined $ps->data->{_session_uid} ) {
|
2017-02-27 21:48:00 +01:00
|
|
|
$ps->update( { _session_uid => $uid } );
|
2016-04-04 22:39:22 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# Set _utime if not already present
|
|
|
|
unless ( defined $ps->data->{_utime} ) {
|
2017-02-27 21:48:00 +01:00
|
|
|
$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
|
|
|
|
return () unless ( ref $infos eq 'HASH' and %$infos );
|
|
|
|
|
2017-02-07 13:52:56 +01:00
|
|
|
$uid ||= $req->{sessionInfo}->{ $self->conf->{whatToTrace} }
|
|
|
|
|| $req->userData->{ $self->conf->{whatToTrace} };
|
|
|
|
unless ($uid) {
|
2017-02-15 07:41:50 +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 );
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
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 " . $self->_md5hash($uid) );
|
|
|
|
$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 datas with $info.
|
|
|
|
# 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
|
|
|
|
$id ||= $req->{id};
|
|
|
|
|
|
|
|
if ($id) {
|
|
|
|
|
|
|
|
# Update sessionInfo data
|
|
|
|
## sessionInfo updated if $id defined : quite strange !!
|
|
|
|
## See http://jira.ow2.org/browse/LEMONLDAP-430
|
|
|
|
foreach ( keys %$infos ) {
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug(
|
|
|
|
"Update sessionInfo $_ with " . $infos->{$_} );
|
2016-06-01 19:36:51 +02:00
|
|
|
$req->{sessionInfo}->{$_} = $infos->{$_};
|
|
|
|
}
|
|
|
|
|
2017-02-20 22:00:05 +01:00
|
|
|
# Update session in global storage with updateTime
|
|
|
|
$infos->{updateTime} = strftime( "%Y%m%d%H%M%S", localtime() );
|
|
|
|
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
|
2016-07-12 18:33:20 +02:00
|
|
|
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,
|
|
|
|
expires => '-1d',
|
|
|
|
)
|
|
|
|
) unless ($preserveCookie);
|
2016-04-14 21:49:27 +02:00
|
|
|
}
|
|
|
|
|
2016-12-26 10:23:31 +01:00
|
|
|
HANDLER->localUnlog( $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 => 0,
|
|
|
|
expires => '-1d',
|
|
|
|
)
|
|
|
|
) unless ($preserveCookie);
|
2016-04-14 21:49:27 +02:00
|
|
|
|
|
|
|
# Log
|
|
|
|
my $user = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
|
2017-02-15 15:16:59 +01:00
|
|
|
$self->userLogger->notice("User $user has been disconnected") if $user;
|
2016-04-14 21:49:27 +02:00
|
|
|
|
|
|
|
return $session->error ? 0 : 1;
|
|
|
|
}
|
|
|
|
|
2016-04-04 22:39:22 +02:00
|
|
|
# Return md5(s)
|
|
|
|
sub _md5hash {
|
|
|
|
my ( $self, $s ) = @_;
|
|
|
|
return substr( Digest::MD5::md5_hex($s), 0, 32 );
|
|
|
|
}
|
|
|
|
|
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;
|
2017-01-10 22:43:34 +01:00
|
|
|
my $res =
|
|
|
|
$self->conf->{cipher} ? $self->conf->{cipher}->encrypt( time() ) : 1;
|
2017-01-04 17:36:54 +01:00
|
|
|
$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->{infoFormMethod} = $req->param('method') || "post";
|
|
|
|
return PE_INFO;
|
|
|
|
}
|
|
|
|
|
2016-11-22 21:55:10 +01:00
|
|
|
$req->datas->{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 correponding value
|
|
|
|
# @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;
|
|
|
|
|
|
|
|
# Store value
|
|
|
|
if ($val) {
|
2016-12-02 06:47:38 +01:00
|
|
|
$key = $prefix . $key;
|
2017-01-04 17:36:54 +01:00
|
|
|
$val =~ s/\+/%2B/g;
|
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
|
|
|
|
if ( my $val = $req->param($key) ) {
|
|
|
|
$val = decode_base64($val) if $base64;
|
|
|
|
return $val;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug("Hidden value $val found for key $key");
|
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 {
|
2016-12-17 08:58:53 +01:00
|
|
|
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#;
|
|
|
|
return $pHost . $req->uri;
|
|
|
|
}
|
|
|
|
|
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};
|
|
|
|
$h{expires} //= $self->conf->{cookieExpiration};
|
|
|
|
foreach (qw(domain path expires max_age HttpOnly)) {
|
|
|
|
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 ) = @_;
|
|
|
|
require Data::Dumper;
|
|
|
|
$Data::Dumper::Indent = 0;
|
2017-02-15 07:41:50 +01:00
|
|
|
$self->logger->debug( "Dump: " . Data::Dumper::Dumper($variable) );
|
2017-01-13 15:35:02 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-01-18 23:36:03 +01:00
|
|
|
sub sendHtml {
|
|
|
|
my ( $self, $req, $template, %args ) = @_;
|
|
|
|
push @{ $req->respHeaders },
|
|
|
|
'X-XSS-Protection' => '1; mode=block',
|
|
|
|
'X-Content-Type-Options' => 'nosniff';
|
2017-01-21 10:17:24 +01:00
|
|
|
|
|
|
|
# Set authorizated URL for POST
|
|
|
|
my $csp = $self->csp . "form-action 'self'";
|
2017-01-24 06:10:57 +01:00
|
|
|
if ( my $url = $req->urldc ) {
|
2017-01-21 10:50:59 +01:00
|
|
|
$url =~ s#https?://([^/]+).*#$1#;
|
|
|
|
$csp .= " $url";
|
|
|
|
}
|
2017-01-21 10:17:24 +01:00
|
|
|
my $url = $args{params}->{URL};
|
|
|
|
if ( $url and $url =~ s#https?://([^/]+).*#$1# ) {
|
|
|
|
$csp .= " $url";
|
|
|
|
}
|
|
|
|
$csp .= ';';
|
|
|
|
|
|
|
|
# Deny using portal in frame except if it is required
|
2017-01-20 07:19:54 +01:00
|
|
|
unless ( $req->frame or $self->conf->{portalAntiFrame} == 0 ) {
|
2017-01-21 10:17:24 +01:00
|
|
|
push @{ $req->respHeaders }, 'X-Frame-Options' => 'DENY';
|
|
|
|
$csp .= "frame-ancestors 'none';";
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check if frames need to be embedded
|
|
|
|
my @url;
|
|
|
|
if ( $req->info ) {
|
|
|
|
@url = map { s#https?://([^/]+).*#$1#; $_ }
|
|
|
|
( $req->info =~ /<iframe.*?src="(.*?)"/sg );
|
|
|
|
}
|
|
|
|
if (@url) {
|
|
|
|
$csp .= join( ' ', 'child-src', @url ) . ';';
|
2017-01-18 23:36:03 +01:00
|
|
|
}
|
|
|
|
|
2017-01-21 10:17:24 +01:00
|
|
|
# Set CSP header
|
|
|
|
push @{ $req->respHeaders }, 'Content-Security-Policy' => $csp;
|
|
|
|
|
2017-01-18 23:36:03 +01:00
|
|
|
return $self->SUPER::sendHtml( $req, $template, %args );
|
|
|
|
}
|
|
|
|
|
2017-02-07 23:04:49 +01:00
|
|
|
sub rebuildCookies {
|
|
|
|
my ( $self, $req ) = @_;
|
|
|
|
my @tmp;
|
|
|
|
for ( my $i = 0 ; $i < @{ $req->{respHeaders} } ; $i += 2 ) {
|
|
|
|
push @tmp, $req->respHeaders->[0], $req->respHeaders->[1]
|
|
|
|
unless ( $req->respHeaders->[0] eq 'Set-Cookie' );
|
|
|
|
}
|
|
|
|
$req->{respHeaders} = \@tmp;
|
|
|
|
$self->buildCookie($req);
|
|
|
|
}
|
|
|
|
|
2017-02-08 14:01:02 +01:00
|
|
|
sub tplParams {
|
2017-02-18 07:49:06 +01:00
|
|
|
return (
|
2017-02-28 19:39:56 +01:00
|
|
|
SKIN => $_[0]->getSkin( $_[1] ),
|
2017-02-18 07:49:06 +01:00
|
|
|
PORTAL_URL => $_[0]->conf->{portal},
|
|
|
|
);
|
2017-02-08 14:01:02 +01:00
|
|
|
}
|
|
|
|
|
2017-02-19 08:17:48 +01:00
|
|
|
sub registerLogin {
|
|
|
|
my ( $self, $req ) = @_;
|
2017-02-27 21:48:00 +01:00
|
|
|
return
|
|
|
|
unless ( $self->conf->{loginHistoryEnabled}
|
|
|
|
and defined $req->authResult );
|
2017-02-19 08:17:48 +01:00
|
|
|
my $history = $req->sessionInfo->{loginHistory} ||= {};
|
|
|
|
my $type = ( $req->authResult > 0 ? 'failed' : 'success' ) . 'Login';
|
|
|
|
$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 )
|
|
|
|
if ( $req->authResult );
|
|
|
|
|
|
|
|
# Add current login into history
|
|
|
|
unshift @{ $history->{$type} }, $login;
|
|
|
|
|
|
|
|
# Forget oldest logins
|
|
|
|
splice @{ $history->{$type} }, $self->conf->{ $type . "Number" }
|
|
|
|
if ( scalar @{ $history->{$type} } > $self->conf->{ $type . "Number" } );
|
|
|
|
|
|
|
|
# Save into persistent session
|
|
|
|
$self->updatePersistentSession( $req, { loginHistory => $history, } );
|
|
|
|
}
|
|
|
|
|
|
|
|
# put main session data into a hash ref
|
|
|
|
# @param hashref $session The session to sum up
|
|
|
|
# @return hashref
|
|
|
|
sub _sumUpSession {
|
|
|
|
my ( $self, $session, $withoutUser ) = @_;
|
|
|
|
my $res =
|
|
|
|
$withoutUser
|
|
|
|
? {}
|
|
|
|
: { user => $session->{ $self->conf->{whatToTrace} } };
|
|
|
|
$res->{$_} = $session->{$_}
|
|
|
|
foreach ( "_utime", "ipAddr",
|
|
|
|
keys %{ $self->conf->{sessionDataToRemember} } );
|
|
|
|
return $res;
|
|
|
|
}
|
|
|
|
|
2016-03-29 23:09:55 +02:00
|
|
|
1;
|