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

467 lines
13 KiB
Perl
Raw Normal View History

2016-04-01 07:24:27 +02:00
package Lemonldap::NG::Portal::Main::Process;
2016-04-07 23:31:56 +02:00
our $VERSION = '2.0.0';
package Lemonldap::NG::Portal::Main;
2016-04-01 07:24:27 +02:00
use strict;
2016-04-02 22:17:39 +02:00
use MIME::Base64;
2016-04-04 07:08:26 +02:00
use POSIX qw(strftime);
2016-04-01 07:24:27 +02:00
2016-04-03 08:33:50 +02:00
# Main method
# -----------
# Launch all methods declared in request "steps" array. Methods can be
# declared by their name (in Lemonldap::NG::Portal::Main namespace) or point
# to a subroutine (see Lemonldap::NG::Portal::Main::Run.pm)
sub process {
my ( $self, $req ) = @_;
#$req->error(PE_OK);
my $err = PE_OK;
while ( my $sub = shift @{ $req->steps } ) {
if ( ref $sub ) {
2016-04-04 07:08:26 +02:00
$self->lmLog( "Processing code ref", 'debug' );
2016-05-12 21:02:48 +02:00
last if ( $err = $sub->($req) );
2016-04-03 08:33:50 +02:00
}
else {
2016-04-04 07:08:26 +02:00
$self->lmLog( "Processing $sub", 'debug' );
2016-04-03 08:33:50 +02:00
last if ( $err = $self->$sub($req) );
}
}
2016-05-12 21:02:48 +02:00
$self->lmLog( "Returned error: $err", 'debug' ) if ($err);
2016-04-03 08:33:50 +02:00
return $err;
}
2016-04-02 22:17:39 +02:00
# First process block: check args
# -------------------------------
# For post requests, parse datas
sub restoreArgs {
my ( $self, $req ) = @_;
$req->parseBody;
2016-04-03 08:33:50 +02:00
$req->mustRedirect(1);
return PE_OK;
2016-04-02 22:17:39 +02:00
}
2016-04-14 20:42:59 +02:00
sub importHandlerDatas {
my ( $self, $req ) = @_;
2016-08-02 15:52:29 +02:00
$req->{sessionInfo} = $req->userData;
2016-05-22 19:06:55 +02:00
$req->id( $req->sessionInfo->{_session_id} );
2016-07-12 07:15:26 +02:00
$req->user( $req->sessionInfo->{ $self->conf->{whatToTrace} } );
2016-04-14 20:42:59 +02:00
PE_OK;
}
2016-04-02 22:17:39 +02:00
# Verify url parameter
sub controlUrl {
my ( $self, $req ) = @_;
2016-04-03 18:51:23 +02:00
$req->{datas}->{_url} ||= '';
2016-04-02 22:17:39 +02:00
if ( my $url = $req->param('url') ) {
2016-04-03 08:33:50 +02:00
# REJECT NON BASE64 URL
if ( $req->urlNotBase64 ) {
2016-05-23 18:55:18 +02:00
$req->{urldc} = $url;
2016-04-03 08:33:50 +02:00
}
else {
2016-04-02 22:17:39 +02:00
if ( $url =~ m#[^A-Za-z0-9\+/=]# ) {
$self->lmLog(
"Value must be in BASE64 (param: url | value: $url)",
"warn" );
return PE_BADURL;
}
2016-05-23 18:55:18 +02:00
$req->{urldc} = decode_base64($url);
$req->{urldc} =~ s/[\r\n]//sg;
2016-04-02 22:17:39 +02:00
}
# For logout request, test if Referer comes from an authorizated site
2016-05-22 19:06:55 +02:00
my $tmp = (
$req->param('logout')
2016-05-23 18:55:23 +02:00
? $req->referer
2016-05-23 18:55:18 +02:00
: $req->{urldc}
2016-05-22 19:06:55 +02:00
);
2016-04-02 22:17:39 +02:00
# XSS attack
if (
$self->checkXSSAttack(
$req->param('logout') ? 'HTTP Referer' : 'urldc',
2016-05-23 18:55:18 +02:00
$req->{urldc}
2016-04-02 22:17:39 +02:00
)
)
{
2016-05-23 18:55:18 +02:00
delete $req->{urldc};
2016-04-02 22:17:39 +02:00
return PE_BADURL;
}
# Non protected hosts
if ( $tmp and !$self->isTrustedUrl($tmp) ) {
$self->lmLog(
"URL contains a non protected host (param: "
. ( $req->param('logout') ? 'HTTP Referer' : 'urldc' )
. " | value: $tmp)",
"warn"
);
2016-05-23 18:55:18 +02:00
delete $req->{urldc};
2016-04-02 22:17:39 +02:00
return PE_BADURL;
}
$req->datas->{_url} = $url;
}
PE_OK;
}
2016-04-14 21:49:27 +02:00
sub checkLogout {
my ( $self, $req ) = @_;
if ( $req->param('logout') ) {
2016-05-23 18:55:23 +02:00
$req->steps(
[ @{ $self->beforeLogout }, 'authLogout', 'deleteSession' ] );
2016-04-18 22:23:40 +02:00
}
PE_OK;
}
2016-05-01 09:30:21 +02:00
sub authLogout {
2016-05-23 23:52:32 +02:00
my ( $self, $req ) = @_;
2016-07-02 10:51:00 +02:00
return $self->_authentication->authLogout($req);
2016-05-01 09:30:21 +02:00
}
2016-04-18 22:23:40 +02:00
sub deleteSession {
my ( $self, $req ) = @_;
my $apacheSession = $self->getApacheSession( $req->id );
2016-12-26 10:23:35 +01:00
my $id = $req->id;
2016-12-23 17:03:36 +01:00
unless ($apacheSession) {
$self->lmLog( "Session $id already deleted", 'debug' );
return PE_OK;
}
2016-04-18 22:23:40 +02:00
unless ( $self->_deleteSession( $req, $apacheSession ) ) {
2016-12-23 17:03:36 +01:00
$self->lmLog( "Unable to delete session $id", 'error' );
2016-12-26 10:23:35 +01:00
$self->lmLog( $apacheSession->error, 'error' );
2016-04-18 22:23:40 +02:00
return PE_ERROR;
}
else {
2016-12-26 10:23:35 +01:00
$self->lmLog( "Session $id deleted from global storage", 'debug' );
2016-04-18 22:23:40 +02:00
}
2016-08-09 14:08:49 +02:00
# TODO
2016-04-18 22:23:40 +02:00
# Collect logout services and build hidden iFrames
2016-12-20 11:43:22 +01:00
if ( $req->datas->{logoutServices} and %{ $req->datas->{logoutServices} } )
{
2016-04-18 22:23:40 +02:00
2016-12-20 11:43:22 +01:00
$self->lmLog( "Create iFrames to forward logout to services", 'debug' );
2016-04-18 22:23:40 +02:00
2016-12-22 09:40:50 +01:00
$self->info( $req, '<h3 trmsg="logoutFromOtherApp"></h3>' );
2016-04-18 22:23:40 +02:00
2016-12-20 11:43:22 +01:00
foreach ( keys %{ $req->datas->{logoutServices} } ) {
my $logoutServiceName = $_;
my $logoutServiceUrl =
$req->datas->{logoutServices}->{$logoutServiceName};
2016-04-18 22:23:40 +02:00
2016-12-20 11:43:22 +01:00
$self->lmLog(
2016-12-22 09:40:50 +01:00
"Find logout service $logoutServiceName ($logoutServiceUrl)",
2016-04-14 21:49:27 +02:00
2016-12-20 11:43:22 +01:00
'debug'
);
2016-04-14 21:49:27 +02:00
2016-12-20 11:43:22 +01:00
my $iframe =
"<iframe src=\"$logoutServiceUrl\""
. " alt=\"$logoutServiceName\" marginwidth=\"0\""
. " marginheight=\"0\" scrolling=\"no\" style=\"border: none;display: hidden;margin: 0\""
. " width=\"0\" height=\"0\" frameborder=\"0\">"
. "</iframe>";
2016-04-14 21:49:27 +02:00
2016-12-22 09:40:50 +01:00
$self->info( $req, $iframe );
2016-12-20 11:43:22 +01:00
}
2016-04-14 21:49:27 +02:00
2016-12-20 11:43:22 +01:00
# Redirect on logout page if no other target defined
if ( !$req->urldc and !$req->postUrl ) {
2016-12-23 17:03:36 +01:00
$self->lmLog( 'No other target defined, redirect on logout',
'debug' );
2016-12-20 11:43:22 +01:00
$req->urldc( $req->scriptname . "?logout=1" );
}
}
2016-04-14 21:49:27 +02:00
2016-04-18 22:23:40 +02:00
# Redirect or Post if asked by authLogout
2016-12-20 11:43:22 +01:00
if ( $req->urldc and $req->urldc ne $self->conf->{portal} ) {
$req->steps( [] );
return PE_REDIRECT;
}
2016-04-14 21:49:27 +02:00
2016-12-20 11:43:22 +01:00
if ( $req->postUrl ) {
$req->steps( ['autoPost'] );
return PE_OK;
}
2016-04-14 21:49:27 +02:00
2016-04-18 22:23:40 +02:00
# If logout redirects to another URL, just remove next steps for the
# request so autoRedirect will be called
if ( $req->{urldc} and $req->{urldc} ne $self->conf->{portal} ) {
$req->steps( [] );
return PE_OK;
2016-04-14 21:49:27 +02:00
}
2016-04-18 22:23:40 +02:00
# Else display "error"
return PE_LOGOUT_OK;
2016-04-14 21:49:27 +02:00
}
# Check value to detect XSS attack
# @param name Parameter name
# @param value Parameter value
# @return 1 if attack detected, 0 else
sub checkXSSAttack {
my ( $self, $name, $value ) = @_;
# Empty values are not bad
return 0 unless $value;
# Test value
2016-06-29 21:40:42 +02:00
$value =~ s/\%25/\%/g;
if ( $value =~ m/(?:\0|<|'|"|`|\%(?:00|3C|22|27|2C))/ ) {
$self->lmLog( "XSS attack detected (param: $name | value: $value)",
"warn" );
return $self->conf->{checkXSS};
}
return 0;
}
2016-04-02 22:17:39 +02:00
# Second block: auth process (call auth or userDB object)
# -------------------------------------------------------
2016-04-01 07:24:27 +02:00
sub extractFormInfo {
2016-05-23 23:52:32 +02:00
my ( $self, $req ) = @_;
2016-05-25 21:30:43 +02:00
my $ret = $self->_authentication->extractFormInfo($req);
2016-12-01 23:25:05 +01:00
if ( $ret == PE_OK and not( $req->user or $req->continue ) ) {
$self->lmLog(
'Authentication module succeed but has not set $req->user',
'error' );
2016-05-25 21:30:43 +02:00
return PE_ERROR;
}
return $ret;
2016-04-01 07:24:27 +02:00
}
sub getUser {
2016-05-23 23:52:32 +02:00
my ( $self, $req ) = @_;
return $self->_userDB->getUser($req);
2016-04-01 07:24:27 +02:00
}
sub authenticate {
2016-05-23 23:52:32 +02:00
my ( $self, $req ) = @_;
return $self->_authentication->authenticate($req);
2016-04-01 07:24:27 +02:00
}
2016-04-02 22:17:39 +02:00
# Third block: Session data providing
# -----------------------------------
2016-04-01 07:24:27 +02:00
2016-12-01 23:25:05 +01:00
sub setAuthSessionInfo {
my ( $self, $req ) = @_;
my $ret = $self->_authentication->setAuthSessionInfo($req);
2016-12-02 06:47:38 +01:00
if ( $ret == PE_OK
and not( defined $req->sessionInfo->{authenticationLevel} ) )
{
2016-12-01 23:25:05 +01:00
$self->lmLog( 'Authentication level is not set by auth module',
'error' );
}
return $ret;
}
2016-04-01 07:24:27 +02:00
sub setSessionInfo {
my ( $self, $req ) = @_;
2016-04-05 22:46:11 +02:00
# Set _user
2016-12-01 23:25:05 +01:00
$req->{sessionInfo}->{_user} //= $req->{user};
2016-04-05 22:46:11 +02:00
2016-04-01 07:24:27 +02:00
# Get the current user module
2016-04-03 18:27:22 +02:00
$req->{sessionInfo}->{_auth} = $self->getModule( $req, "auth" );
$req->{sessionInfo}->{_userDB} = $self->getModule( $req, "user" );
2016-04-01 07:24:27 +02:00
# Store IP address from remote address or X-FORWARDED-FOR header
$req->{sessionInfo}->{ipAddr} = $req->remote_ip;
# Date and time
if ( $self->conf->{updateSession} ) {
$req->{sessionInfo}->{updateTime} =
strftime( "%Y%m%d%H%M%S", localtime() );
}
else {
$req->{sessionInfo}->{_utime} ||= time();
$req->{sessionInfo}->{startTime} =
strftime( "%Y%m%d%H%M%S", localtime() );
2016-04-02 22:17:39 +02:00
$req->{sessionInfo}->{_lastSeen} = time()
if $self->conf->{timeoutActivity};
2016-04-01 07:24:27 +02:00
}
2016-08-09 14:08:49 +02:00
# Get environment variables matching exportedVars (works only with HTTP_*
# and SSL_*: see Main/Request.pm)
2016-04-01 07:24:27 +02:00
foreach ( keys %{ $self->conf->{exportedVars} } ) {
2016-08-09 14:08:49 +02:00
if ( my $tmp = $req->{ $self->conf->{exportedVars}->{$_} } ) {
2016-04-01 07:24:27 +02:00
$tmp =~ s/[\r\n]/ /gs;
$req->{sessionInfo}->{$_} = $tmp;
}
}
# Store URL origin in session
2016-05-23 18:55:18 +02:00
$req->{sessionInfo}->{_url} = $req->{urldc};
2016-04-01 07:24:27 +02:00
# Share sessionInfo with underlying handler (needed for safe jail)
2016-08-03 09:31:36 +02:00
$req->userData( $req->sessionInfo );
2016-04-01 07:24:27 +02:00
# Call UserDB setSessionInfo
2016-04-03 08:33:50 +02:00
return $self->_userDB->setSessionInfo($req);
2016-04-01 07:24:27 +02:00
PE_OK;
}
sub setMacros {
2016-04-03 08:33:50 +02:00
my ( $self, $req ) = @_;
foreach ( sort keys %{ $self->_macros } ) {
2016-12-20 11:43:22 +01:00
$req->{sessionInfo}->{$_} =
$self->_macros->{$_}->( $req->sessionInfo );
2016-04-03 08:33:50 +02:00
}
PE_OK;
2016-04-01 07:24:27 +02:00
}
sub setGroups {
2016-04-03 08:33:50 +02:00
my ( $self, $req ) = @_;
2016-05-23 23:52:32 +02:00
return $self->_userDB->setGroups($req);
2016-04-01 07:24:27 +02:00
}
sub setPersistentSessionInfo {
2016-04-03 08:33:50 +02:00
my ( $self, $req ) = @_;
2016-04-01 12:10:42 +02:00
2016-04-03 08:33:50 +02:00
# Do not restore infos if session already opened
unless ( $req->{id} ) {
my $key = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
2016-04-01 12:10:42 +02:00
2016-04-03 08:33:50 +02:00
return PE_OK unless ( $key and length($key) );
2016-04-01 12:10:42 +02:00
2016-04-03 08:33:50 +02:00
my $persistentSession = $self->getPersistentSession($key);
2016-04-01 12:10:42 +02:00
2016-04-03 08:33:50 +02:00
if ($persistentSession) {
$self->lmLog( "Persistent session found for $key", 'debug' );
foreach my $k ( keys %{ $persistentSession->data } ) {
2016-04-01 12:10:42 +02:00
2016-04-03 08:33:50 +02:00
# Do not restore some parameters
next if $k =~ /^_(?:utime|session_(?:u?id|kind))$/;
$self->lmLog( "Restore persistent parameter $k", 'debug' );
$req->{sessionInfo}->{$k} = $persistentSession->data->{$k};
}
}
}
2016-04-01 12:10:42 +02:00
2016-04-03 08:33:50 +02:00
PE_OK;
2016-04-01 07:24:27 +02:00
}
sub setLocalGroups {
2016-04-03 08:33:50 +02:00
my ( $self, $req ) = @_;
foreach ( sort keys %{ $self->_groups } ) {
2016-08-02 15:52:29 +02:00
if ( $self->_groups->{$_}->( $req->sessionInfo ) ) {
2016-04-03 08:33:50 +02:00
$req->{sessionInfo}->{groups} .=
$self->conf->{multiValuesSeparator} . $_;
$req->{sessionInfo}->{hGroups}->{$_}->{name} = $_;
}
}
# Clear values separator at the beginning
if ( $req->{sessionInfo}->{groups} ) {
$req->{sessionInfo}->{groups} =~
2016-11-29 06:43:46 +01:00
s/^$self->conf->{multiValuesSeparator}//o;
2016-04-03 08:33:50 +02:00
}
PE_OK;
2016-04-01 07:24:27 +02:00
}
sub store {
2016-04-03 08:33:50 +02:00
my ( $self, $req ) = @_;
# Now, user is authenticated => inform handler
$req->userData( $req->sessionInfo );
# Create second session for unsecure cookie
if ( $self->conf->{securedCookie} == 2 ) {
my $session2 = $self->getApacheSession( undef, 1 );
my %infos = %{ $req->{sessionInfo} };
$infos{_httpSessionType} = 1;
$session2->update( \%infos );
$req->{sessionInfo}->{_httpSession} = $session2->id;
}
# Main session
my $session = $self->getApacheSession( $req->{id}, 0, $self->{force} );
return PE_APACHESESSIONERROR unless ($session);
2016-04-04 22:39:22 +02:00
$req->id( $session->{id} );
2016-04-03 08:33:50 +02:00
# Compute unsecure cookie value if needed
if ( $self->conf->{securedCookie} == 3 ) {
$req->{sessionInfo}->{_httpSession} =
$self->conf->{cipher}->encryptHex( $self->{id}, "http" );
}
# Fill session
my $infos = {};
foreach my $k ( keys %{ $req->{sessionInfo} } ) {
next unless defined $req->{sessionInfo}->{$k};
my $displayValue = $req->{sessionInfo}->{$k};
2016-04-04 22:39:22 +02:00
if ( $self->conf->{hiddenAttributes}
and $self->conf->{hiddenAttributes} =~ /\b$k\b/ )
{
2016-04-03 08:33:50 +02:00
$displayValue = '****';
}
$self->lmLog( "Store $displayValue in session key $k", 'debug' );
$self->_dump($displayValue) if ref($displayValue);
$infos->{$k} = $req->{sessionInfo}->{$k};
2016-04-03 08:33:50 +02:00
}
$session->update($infos);
PE_OK;
2016-04-01 07:24:27 +02:00
}
sub buildCookie {
2016-04-03 08:33:50 +02:00
my ( $self, $req ) = @_;
2016-04-04 22:39:22 +02:00
push @{ $req->respHeaders },
'Set-Cookie' => $self->cookie(
name => $self->conf->{cookieName},
value => $req->{id},
domain => $self->conf->{domain},
2016-04-03 08:33:50 +02:00
path => "/",
2016-04-04 22:39:22 +02:00
secure => $self->conf->{securedCookie},
HttpOnly => $self->conf->{httpOnly},
expires => $self->conf->{cookieExpiration},
2016-04-03 08:33:50 +02:00
);
if ( $self->conf->{securedCookie} >= 2 ) {
2016-04-04 22:39:22 +02:00
push @{ $req->respHeaders },
'Set-Cookie' => $self->cookie(
name => $self->conf->{cookieName} . "http",
value => $req->{sessionInfo}->{_httpSession},
domain => $self->conf->{domain},
2016-04-03 08:33:50 +02:00
path => "/",
secure => 0,
2016-04-04 22:39:22 +02:00
HttpOnly => $self->conf->{httpOnly},
expires => $self->conf->{cookieExpiration},
2016-04-03 08:33:50 +02:00
);
}
PE_OK;
2016-04-01 12:10:42 +02:00
}
sub cookie {
2016-04-03 08:33:50 +02:00
my ( $self, %h ) = @_;
my @res;
$res[0] = "$h{name}" or die("name required");
$res[0] .= "=$h{value}";
foreach (qw(domain path expires max_age)) {
my $f = $_;
2016-04-04 22:39:22 +02:00
$f =~ s/_/-/g;
push @res, "$f=$h{$_}" if ( $h{$_} );
2016-04-03 08:33:50 +02:00
}
return join( '; ', @res );
2016-04-01 07:24:27 +02:00
}
sub _dump {
2016-07-02 10:51:00 +02:00
my ( $self, $variable ) = @_;
require Data::Dumper;
$Data::Dumper::Indent = 0;
$self->lmLog( "Dump: " . Data::Dumper::Dumper($variable), 'debug' );
return;
}
2016-04-01 07:24:27 +02:00
1;