Process.pm 13.2 KB
Newer Older
Yadd's avatar
Yadd committed
1 2
package Lemonldap::NG::Portal::Main::Process;

Yadd's avatar
Yadd committed
3 4 5 6
our $VERSION = '2.0.0';

package Lemonldap::NG::Portal::Main;

Yadd's avatar
Yadd committed
7
use strict;
Yadd's avatar
Yadd committed
8
use MIME::Base64;
Yadd's avatar
Yadd committed
9
use POSIX qw(strftime);
Yadd's avatar
Yadd committed
10

Yadd's avatar
Yadd committed
11 12 13 14 15 16 17 18 19 20 21 22 23
# 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 ) {
Yadd's avatar
Yadd committed
24
            $self->logger->debug("Processing code ref");
Yadd's avatar
Yadd committed
25
            last if ( $err = $sub->($req) );
Yadd's avatar
Yadd committed
26 27
        }
        else {
Yadd's avatar
Yadd committed
28
            $self->logger->debug("Processing $sub");
Yadd's avatar
Yadd committed
29 30 31
            last if ( $err = $self->$sub($req) );
        }
    }
Yadd's avatar
Yadd committed
32
    $self->logger->debug("Returned error: $err") if ($err);
Yadd's avatar
Yadd committed
33 34 35
    return $err;
}

Yadd's avatar
Yadd committed
36 37 38 39 40 41
# First process block: check args
# -------------------------------

# For post requests, parse datas
sub restoreArgs {
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
42
    $req->mustRedirect(1);
43
    return PE_OK;
Yadd's avatar
Yadd committed
44 45
}

Yadd's avatar
Yadd committed
46 47
sub importHandlerDatas {
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
48
    $req->{sessionInfo} = $req->userData;
Yadd's avatar
Yadd committed
49
    $req->id( $req->sessionInfo->{_session_id} );
Yadd's avatar
Yadd committed
50
    $req->user( $req->sessionInfo->{ $self->conf->{whatToTrace} } );
Yadd's avatar
Yadd committed
51 52 53
    PE_OK;
}

Yadd's avatar
Yadd committed
54
# Verify url and confirm parameter
Yadd's avatar
Yadd committed
55 56
sub controlUrl {
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
57 58 59 60 61 62 63 64 65 66
    if ( my $c = $req->param('confirm') ) {

        # Replace confirm stamp by 1 or -1
        $c =~ s/^(-?)(.*)$/${1}1/;

        # Decrypt confirm stamp if cipher available
        # and confirm not already decrypted
        if ( $self->conf->{cipher} and $2 ne "1" ) {
            my $time = time() - $self->conf->{cipher}->decrypt($2);
            if ( $time < 600 ) {
Yadd's avatar
Yadd committed
67
                $self->logger->debug("Confirm parameter accepted $c");
Yadd's avatar
Yadd committed
68
                $req->set_param( 'confirm', $c );
Yadd's avatar
Yadd committed
69 70
            }
            else {
Yadd's avatar
Yadd committed
71
                $self->logger->notice('Confirmation to old, refused');
Yadd's avatar
Yadd committed
72
                $req->set_param( 'confirm', 0 );
Yadd's avatar
Yadd committed
73 74 75
            }
        }
    }
Yadd's avatar
Yadd committed
76
    $req->{datas}->{_url} ||= '';
Yadd's avatar
Yadd committed
77 78
    if ( my $url = $req->param('url') ) {

Yadd's avatar
Yadd committed
79 80
        # REJECT NON BASE64 URL
        if ( $req->urlNotBase64 ) {
Yadd's avatar
Yadd committed
81
            $req->{urldc} = $url;
Yadd's avatar
Yadd committed
82 83
        }
        else {
Yadd's avatar
Yadd committed
84
            if ( $url =~ m#[^A-Za-z0-9\+/=]# ) {
Yadd's avatar
Yadd committed
85 86
                $self->userLogger->error(
                    "Value must be in BASE64 (param: url | value: $url)");
Yadd's avatar
Yadd committed
87 88
                return PE_BADURL;
            }
Yadd's avatar
Yadd committed
89 90
            $req->{urldc} = decode_base64($url);
            $req->{urldc} =~ s/[\r\n]//sg;
Yadd's avatar
Yadd committed
91 92 93
        }

        # For logout request, test if Referer comes from an authorizated site
Yadd's avatar
Yadd committed
94 95
        my $tmp = (
              $req->param('logout')
Yadd's avatar
Yadd committed
96
            ? $req->referer
Yadd's avatar
Yadd committed
97
            : $req->{urldc}
Yadd's avatar
Yadd committed
98
        );
Yadd's avatar
Yadd committed
99 100 101 102 103

        # XSS attack
        if (
            $self->checkXSSAttack(
                $req->param('logout') ? 'HTTP Referer' : 'urldc',
Yadd's avatar
Yadd committed
104
                $req->{urldc}
Yadd's avatar
Yadd committed
105 106 107
            )
          )
        {
Yadd's avatar
Yadd committed
108
            delete $req->{urldc};
Yadd's avatar
Yadd committed
109 110 111 112 113
            return PE_BADURL;
        }

        # Non protected hosts
        if ( $tmp and !$self->isTrustedUrl($tmp) ) {
Yadd's avatar
Yadd committed
114 115
            $self->userLogger->error(
                    "URL contains a non protected host (param: "
Yadd's avatar
Yadd committed
116
                  . ( $req->param('logout') ? 'HTTP Referer' : 'urldc' )
Yadd's avatar
Yadd committed
117
                  . " | value: $tmp)" );
Yadd's avatar
Yadd committed
118
            delete $req->{urldc};
Yadd's avatar
Yadd committed
119 120 121 122 123 124 125 126 127
            return PE_BADURL;
        }

        $req->datas->{_url} = $url;
    }

    PE_OK;
}

Yadd's avatar
Yadd committed
128 129
sub checkLogout {
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
130
    if ( defined $req->param('logout') ) {
Yadd's avatar
Yadd committed
131 132
        $req->steps(
            [ @{ $self->beforeLogout }, 'authLogout', 'deleteSession' ] );
Yadd's avatar
Yadd committed
133 134 135 136
    }
    PE_OK;
}

Yadd's avatar
Yadd committed
137
sub authLogout {
Yadd's avatar
Yadd committed
138
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
139
    return $self->_authentication->authLogout($req);
Yadd's avatar
Yadd committed
140 141
}

Yadd's avatar
Yadd committed
142 143
sub deleteSession {
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
144
    $req->userData( {} );
Yadd's avatar
Yadd committed
145
    my $apacheSession = $self->getApacheSession( $req->id );
Yadd's avatar
Yadd committed
146
    my $id            = $req->id;
Yadd's avatar
Yadd committed
147
    unless ($apacheSession) {
Yadd's avatar
Yadd committed
148
        $self->logger->debug("Session $id already deleted");
Yadd's avatar
Yadd committed
149 150
        return PE_OK;
    }
Yadd's avatar
Yadd committed
151
    unless ( $self->_deleteSession( $req, $apacheSession ) ) {
Yadd's avatar
Yadd committed
152 153
        $self->logger->error("Unable to delete session $id");
        $self->logger->error( $apacheSession->error );
Yadd's avatar
Yadd committed
154 155 156
        return PE_ERROR;
    }
    else {
Yadd's avatar
Yadd committed
157
        $self->logger->debug("Session $id deleted from global storage");
Yadd's avatar
Yadd committed
158 159
    }

Yadd's avatar
Yadd committed
160
    # TODO
Yadd's avatar
Yadd committed
161
    # Collect logout services and build hidden iFrames
Yadd's avatar
Yadd committed
162 163
    if ( $req->datas->{logoutServices} and %{ $req->datas->{logoutServices} } )
    {
Yadd's avatar
Yadd committed
164

Yadd's avatar
Yadd committed
165
        $self->logger->debug("Create iFrames to forward logout to services");
Yadd's avatar
Yadd committed
166

Yadd's avatar
Yadd committed
167
        $req->info('<h3 trmsg="logoutFromOtherApp"></h3>');
Yadd's avatar
Yadd committed
168

Yadd's avatar
Yadd committed
169 170 171 172
        foreach ( keys %{ $req->datas->{logoutServices} } ) {
            my $logoutServiceName = $_;
            my $logoutServiceUrl =
              $req->datas->{logoutServices}->{$logoutServiceName};
Yadd's avatar
Yadd committed
173

Yadd's avatar
Yadd committed
174 175
            $self->logger->debug(
                "Find logout service $logoutServiceName ($logoutServiceUrl)");
Yadd's avatar
Yadd committed
176

Yadd's avatar
Yadd committed
177
            my $iframe =
Yadd's avatar
Yadd committed
178 179 180 181
                qq'<iframe src="$logoutServiceUrl" alt="$logoutServiceName"'
              . ' marginwidth="0" marginheight="0" scrolling="no"'
              . ' class="hiddenFrame" width="0" height="0"'
              . ' frameborder="0"></iframe>';
Yadd's avatar
Yadd committed
182

Yadd's avatar
Yadd committed
183
            $req->info($iframe);
Yadd's avatar
Yadd committed
184
        }
Yadd's avatar
Yadd committed
185

Yadd's avatar
Yadd committed
186 187
        # Redirect on logout page if no other target defined
        if ( !$req->urldc and !$req->postUrl ) {
Yadd's avatar
Yadd committed
188
            $self->logger->debug('No other target defined, redirect on logout');
Yadd's avatar
Yadd committed
189
            $req->urldc( $req->script_name . "?logout=1" );
Yadd's avatar
Yadd committed
190 191
        }
    }
Yadd's avatar
Yadd committed
192

Yadd's avatar
Yadd committed
193
    # Redirect or Post if asked by authLogout
Yadd's avatar
Yadd committed
194 195 196 197
    if ( $req->urldc and $req->urldc ne $self->conf->{portal} ) {
        $req->steps( [] );
        return PE_REDIRECT;
    }
Yadd's avatar
Yadd committed
198

Yadd's avatar
Yadd committed
199 200 201 202
    if ( $req->postUrl ) {
        $req->steps( ['autoPost'] );
        return PE_OK;
    }
Yadd's avatar
Yadd committed
203

Yadd's avatar
Yadd committed
204 205 206 207 208
    # 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;
Yadd's avatar
Yadd committed
209 210
    }

Yadd's avatar
Yadd committed
211 212
    # Else display "error"
    return PE_LOGOUT_OK;
Yadd's avatar
Yadd committed
213 214
}

Yadd's avatar
Yadd committed
215 216 217 218 219 220 221 222 223 224 225
# 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
Yadd's avatar
Yadd committed
226 227
    $value =~ s/\%25/\%/g;
    if ( $value =~ m/(?:\0|<|'|"|`|\%(?:00|3C|22|27|2C))/ ) {
Yadd's avatar
Yadd committed
228 229
        $self->userLogger->error(
            "XSS attack detected (param: $name | value: $value)");
Yadd's avatar
Yadd committed
230 231 232 233 234 235
        return $self->conf->{checkXSS};
    }

    return 0;
}

Yadd's avatar
Yadd committed
236 237 238
# Second block: auth process (call auth or userDB object)
# -------------------------------------------------------

Yadd's avatar
Yadd committed
239
sub extractFormInfo {
Yadd's avatar
Yadd committed
240
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
241
    my $ret = $self->_authentication->extractFormInfo($req);
Yadd's avatar
Yadd committed
242
    if ( $ret == PE_OK and not( $req->user or $req->continue ) ) {
Yadd's avatar
Yadd committed
243 244
        $self->logger->error(
            'Authentication module succeed but has not set $req->user');
Yadd's avatar
Yadd committed
245 246
        return PE_ERROR;
    }
247 248 249
    elsif ( $ret == PE_FIRSTACCESS
        and $req->cookies->{ $self->conf->{cookieName} } )
    {
Yadd's avatar
Yadd committed
250 251 252 253 254 255 256 257 258
        $req->addCookie(
            $self->cookie(
                name    => $self->conf->{cookieName},
                value   => 0,
                domain  => $self->conf->{domain},
                secure  => 0,
                expires => '-1d',
            )
        );
259 260
        return PE_SESSIONEXPIRED;
    }
Yadd's avatar
Yadd committed
261
    return $ret;
Yadd's avatar
Yadd committed
262 263 264
}

sub getUser {
Yadd's avatar
Yadd committed
265 266
    my ( $self, $req ) = @_;
    return $self->_userDB->getUser($req);
Yadd's avatar
Yadd committed
267 268 269
}

sub authenticate {
Yadd's avatar
Yadd committed
270
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
271
    return $req->authResult( $self->_authentication->authenticate($req) );
Yadd's avatar
Yadd committed
272 273
}

Yadd's avatar
Yadd committed
274 275
# Third block: Session data providing
# -----------------------------------
Yadd's avatar
Yadd committed
276

Yadd's avatar
Yadd committed
277 278 279
sub setAuthSessionInfo {
    my ( $self, $req ) = @_;
    my $ret = $self->_authentication->setAuthSessionInfo($req);
Yadd's avatar
Yadd committed
280 281 282
    if ( $ret == PE_OK
        and not( defined $req->sessionInfo->{authenticationLevel} ) )
    {
Yadd's avatar
Yadd committed
283
        $self->logger->error('Authentication level is not set by auth module');
Yadd's avatar
Yadd committed
284 285 286 287
    }
    return $ret;
}

Yadd's avatar
Yadd committed
288 289 290
sub setSessionInfo {
    my ( $self, $req ) = @_;

Yadd's avatar
Yadd committed
291
    # Set _user
Yadd's avatar
Yadd committed
292
    $req->{sessionInfo}->{_user} //= $req->{user};
Yadd's avatar
Yadd committed
293

Yadd's avatar
Yadd committed
294
    # Get the current user module
Yadd's avatar
Yadd committed
295 296
    $req->{sessionInfo}->{_auth}   = $self->getModule( $req, "auth" );
    $req->{sessionInfo}->{_userDB} = $self->getModule( $req, "user" );
Yadd's avatar
Yadd committed
297 298

    # Store IP address from remote address or X-FORWARDED-FOR header
Yadd's avatar
Yadd committed
299
    $req->{sessionInfo}->{ipAddr} = $req->address;
Yadd's avatar
Yadd committed
300 301 302 303 304 305 306 307 308 309

    # 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() );
Yadd's avatar
Yadd committed
310 311
        $req->{sessionInfo}->{_lastSeen} = time()
          if $self->conf->{timeoutActivity};
Yadd's avatar
Yadd committed
312 313
    }

Yadd's avatar
Yadd committed
314 315
    # Get environment variables matching exportedVars (works only with HTTP_*
    # and SSL_*: see Main/Request.pm)
Yadd's avatar
Yadd committed
316
    foreach ( keys %{ $self->conf->{exportedVars} } ) {
Yadd's avatar
Yadd committed
317
        if ( my $tmp = $req->{ $self->conf->{exportedVars}->{$_} } ) {
Yadd's avatar
Yadd committed
318 319 320 321 322 323
            $tmp =~ s/[\r\n]/ /gs;
            $req->{sessionInfo}->{$_} = $tmp;
        }
    }

    # Store URL origin in session
Yadd's avatar
Yadd committed
324
    $req->{sessionInfo}->{_url} = $req->{urldc};
Yadd's avatar
Yadd committed
325

326
    # Share sessionInfo with underlying handler (needed for safe jail)
Yadd's avatar
Yadd committed
327
    $req->userData( $req->sessionInfo );
328

Yadd's avatar
Yadd committed
329
    # Call UserDB setSessionInfo
Yadd's avatar
Yadd committed
330
    return $self->_userDB->setSessionInfo($req);
Yadd's avatar
Yadd committed
331 332 333 334 335

    PE_OK;
}

sub setMacros {
Yadd's avatar
Yadd committed
336 337
    my ( $self, $req ) = @_;
    foreach ( sort keys %{ $self->_macros } ) {
Yadd's avatar
Yadd committed
338 339
        $req->{sessionInfo}->{$_} =
          $self->_macros->{$_}->( $req->sessionInfo );
Yadd's avatar
Yadd committed
340 341
    }
    PE_OK;
Yadd's avatar
Yadd committed
342 343 344
}

sub setGroups {
Yadd's avatar
Yadd committed
345
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
346
    return $self->_userDB->setGroups($req);
Yadd's avatar
Yadd committed
347 348 349
}

sub setPersistentSessionInfo {
Yadd's avatar
Yadd committed
350
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
351

Yadd's avatar
Yadd committed
352 353 354
    # Do not restore infos if session already opened
    unless ( $req->{id} ) {
        my $key = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
Yadd's avatar
Yadd committed
355

Yadd's avatar
Yadd committed
356
        return PE_OK unless ( $key and length($key) );
Yadd's avatar
Yadd committed
357

Yadd's avatar
Yadd committed
358
        my $persistentSession = $self->getPersistentSession($key);
Yadd's avatar
Yadd committed
359

Yadd's avatar
Yadd committed
360
        if ($persistentSession) {
Yadd's avatar
Yadd committed
361
            $self->logger->debug("Persistent session found for $key");
Yadd's avatar
Yadd committed
362
            foreach my $k ( keys %{ $persistentSession->data } ) {
Yadd's avatar
Yadd committed
363

Yadd's avatar
Yadd committed
364 365
                # Do not restore some parameters
                next if $k =~ /^_(?:utime|session_(?:u?id|kind))$/;
Yadd's avatar
Yadd committed
366
                $self->logger->debug("Restore persistent parameter $k");
Yadd's avatar
Yadd committed
367 368 369 370
                $req->{sessionInfo}->{$k} = $persistentSession->data->{$k};
            }
        }
    }
Yadd's avatar
Yadd committed
371

Yadd's avatar
Yadd committed
372
    PE_OK;
Yadd's avatar
Yadd committed
373 374 375
}

sub setLocalGroups {
Yadd's avatar
Yadd committed
376 377
    my ( $self, $req ) = @_;
    foreach ( sort keys %{ $self->_groups } ) {
Yadd's avatar
Yadd committed
378
        if ( $self->_groups->{$_}->( $req->sessionInfo ) ) {
Yadd's avatar
Yadd committed
379 380 381 382 383 384 385 386 387
            $req->{sessionInfo}->{groups} .=
              $self->conf->{multiValuesSeparator} . $_;
            $req->{sessionInfo}->{hGroups}->{$_}->{name} = $_;
        }
    }

    # Clear values separator at the beginning
    if ( $req->{sessionInfo}->{groups} ) {
        $req->{sessionInfo}->{groups} =~
Yadd's avatar
Yadd committed
388
          s/^$self->conf->{multiValuesSeparator}//o;
Yadd's avatar
Yadd committed
389 390
    }
    PE_OK;
Yadd's avatar
Yadd committed
391 392 393
}

sub store {
Yadd's avatar
Yadd committed
394 395 396 397 398 399 400
    my ( $self, $req ) = @_;

    # Now, user is authenticated => inform handler
    $req->userData( $req->sessionInfo );

    # Create second session for unsecure cookie
    if ( $self->conf->{securedCookie} == 2 ) {
Yadd's avatar
Yadd committed
401
        my $session2 = $self->getApacheSession(undef);
Yadd's avatar
Yadd committed
402 403 404 405 406 407 408 409 410 411

        my %infos = %{ $req->{sessionInfo} };
        $infos{_httpSessionType} = 1;

        $session2->update( \%infos );

        $req->{sessionInfo}->{_httpSession} = $session2->id;
    }

    # Main session
Yadd's avatar
Yadd committed
412
    my $session =
Yadd's avatar
Yadd committed
413
      $self->getApacheSession( $req->{id}, force => $req->{force} );
Yadd's avatar
Yadd committed
414
    return PE_APACHESESSIONERROR unless ($session);
Yadd's avatar
Yadd committed
415
    $req->id( $session->{id} );
Yadd's avatar
Yadd committed
416 417 418 419 420 421 422 423 424 425 426 427

    # 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};
Yadd's avatar
Yadd committed
428 429 430
        if (    $self->conf->{hiddenAttributes}
            and $self->conf->{hiddenAttributes} =~ /\b$k\b/ )
        {
Yadd's avatar
Yadd committed
431 432
            $displayValue = '****';
        }
Yadd's avatar
Yadd committed
433
        $self->logger->debug("Store $displayValue in session key $k");
Yadd's avatar
Yadd committed
434
        $self->_dump($displayValue) if ref($displayValue);
Yadd's avatar
Yadd committed
435
        $infos->{$k} = $req->{sessionInfo}->{$k};
Yadd's avatar
Yadd committed
436 437 438 439
    }
    $session->update($infos);

    PE_OK;
Yadd's avatar
Yadd committed
440 441 442
}

sub buildCookie {
Yadd's avatar
Yadd committed
443
    my ( $self, $req ) = @_;
Yadd's avatar
Yadd committed
444
    if ( $req->id ) {
Yadd's avatar
Yadd committed
445 446
        $req->addCookie(
            $self->cookie(
Yadd's avatar
Yadd committed
447 448
                name   => $self->conf->{cookieName},
                value  => $req->{id},
Yadd's avatar
Yadd committed
449
                domain => $self->conf->{domain},
Yadd's avatar
Yadd committed
450
                secure => $self->conf->{securedCookie},
Yadd's avatar
Yadd committed
451 452
            )
        );
Yadd's avatar
Yadd committed
453 454 455 456 457 458 459 460 461 462
        if ( $self->conf->{securedCookie} >= 2 ) {
            $req->addCookie(
                $self->cookie(
                    name   => $self->conf->{cookieName} . "http",
                    value  => $req->{sessionInfo}->{_httpSession},
                    domain => $self->conf->{domain},
                    secure => 0,
                )
            );
        }
Yadd's avatar
Yadd committed
463 464
    }
    PE_OK;
Yadd's avatar
Yadd committed
465 466
}

Yadd's avatar
Yadd committed
467
1;