#595 in progress

This commit is contained in:
Xavier Guimard 2016-04-03 06:33:50 +00:00
parent eb4b72168b
commit d3d6410646
6 changed files with 220 additions and 159 deletions

View File

@ -7,6 +7,6 @@ our $VERSION = '2.0.0';
extends 'Lemonldap::NG::Portal::Main::Module'; extends 'Lemonldap::NG::Portal::Main::Module';
has authnLevel => (is => 'rw'); has authnLevel => ( is => 'rw' );
1; 1;

View File

@ -27,8 +27,8 @@ has _authentication => ( is => 'rw' );
has _userDB => ( is => 'rw' ); has _userDB => ( is => 'rw' );
# Macros and groups # Macros and groups
has _macros => (is => 'rw'); has _macros => ( is => 'rw' );
has _groups => (is => 'rw'); has _groups => ( is => 'rw' );
# Lists to store plugins entry-points # Lists to store plugins entry-points
has beforeAuth => ( has beforeAuth => (
@ -91,7 +91,10 @@ sub reloadConf {
} }
# Reinitialize arrays # Reinitialize arrays
foreach (qw(_macros _groups beforeAuth betweenAuthAndDatas afterDatas forAuthUser)) { foreach (
qw(_macros _groups beforeAuth betweenAuthAndDatas afterDatas forAuthUser)
)
{
$self->{$_} = []; $self->{$_} = [];
} }
@ -160,8 +163,7 @@ sub loadPlugin {
my ( $self, $plugin ) = @_; my ( $self, $plugin ) = @_;
my $obj; my $obj;
return 0 return 0
unless ( $obj = unless ( $obj = $self->loadModule("$plugin") );
$self->loadModule("$plugin") );
foreach my $sub ( foreach my $sub (
qw(beforeAuthProcess addSessionData afterAuthProcess forAuthUser)) qw(beforeAuthProcess addSessionData afterAuthProcess forAuthUser))
{ {
@ -177,7 +179,7 @@ sub loadPlugin {
sub loadModule { sub loadModule {
my ( $self, $module ) = @_; my ( $self, $module ) = @_;
my $obj; my $obj;
$module = "Lemonldap::NG::Portal$module" if($module =~/^::/); $module = "Lemonldap::NG::Portal$module" if ( $module =~ /^::/ );
eval "require $module"; eval "require $module";
if ($@) { if ($@) {

View File

@ -43,7 +43,7 @@ sub enabledPlugins {
foreach my $type (qw(password register)) { foreach my $type (qw(password register)) {
my $tmp = $self->conf->{$type}; my $tmp = $self->conf->{$type};
if ( $tmp and $tmp ne 'Null' ) { if ( $tmp and $tmp ne 'Null' ) {
$tmp = '::'.ucfirst($type) . "DB::$tmp"; $tmp = '::' . ucfirst($type) . "DB::$tmp";
$self->lmLog("$tmp enabled"); $self->lmLog("$tmp enabled");
push @res, $tmp; push @res, $tmp;
} }

View File

@ -8,6 +8,28 @@ use MIME::Base64;
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
# 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 ) {
last if ( $sub->($req) );
}
else {
last if ( $err = $self->$sub($req) );
}
}
return $err;
}
# First process block: check args # First process block: check args
# ------------------------------- # -------------------------------
@ -15,6 +37,7 @@ our $VERSION = '2.0.0';
sub restoreArgs { sub restoreArgs {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
$req->parseBody; $req->parseBody;
$req->mustRedirect(1);
return ( %{ $req->params } ? PE_OK : PE_FORMEMPTY ); return ( %{ $req->params } ? PE_OK : PE_FORMEMPTY );
} }
@ -24,19 +47,20 @@ sub controlUrl {
$req->datas->{_url} ||= ''; $req->datas->{_url} ||= '';
if ( my $url = $req->param('url') ) { if ( my $url = $req->param('url') ) {
# REJECT NON BASE64 URL except for CAS IssuerDB # REJECT NON BASE64 URL
if ( $self->get_module('issuer') ne "CAS" ) { if ( $req->urlNotBase64 ) {
$req->datas->{urldc} = $url;
}
else {
if ( $url =~ m#[^A-Za-z0-9\+/=]# ) { if ( $url =~ m#[^A-Za-z0-9\+/=]# ) {
$self->lmLog( $self->lmLog(
"Value must be in BASE64 (param: url | value: $url)", "Value must be in BASE64 (param: url | value: $url)",
"warn" ); "warn" );
return PE_BADURL; return PE_BADURL;
} }
$req->datas->{urldc} = decode_base64($url); $req->datas->{urldc} = decode_base64($url);
$req->datas->{urldc} =~ s/[\r\n]//sg; $req->datas->{urldc} =~ s/[\r\n]//sg;
} }
else { $req->datas->{urldc} = $url; }
# For logout request, test if Referer comes from an authorizated site # For logout request, test if Referer comes from an authorizated site
my $tmp = my $tmp =
@ -97,7 +121,8 @@ sub setSessionInfo {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
# Get the current user module # Get the current user module
$req->{sessionInfo}->{_userDB} = $self->get_module("user"); $req->{sessionInfo}->{_auth} = $self->getModule("auth");
$req->{sessionInfo}->{_userDB} = $self->getModule("user");
# Store IP address from remote address or X-FORWARDED-FOR header # Store IP address from remote address or X-FORWARDED-FOR header
$req->{sessionInfo}->{ipAddr} = $req->remote_ip; $req->{sessionInfo}->{ipAddr} = $req->remote_ip;
@ -127,153 +152,153 @@ sub setSessionInfo {
$req->{sessionInfo}->{_url} = $req->datas->{urldc}; $req->{sessionInfo}->{_url} = $req->datas->{urldc};
# Call UserDB setSessionInfo # Call UserDB setSessionInfo
return $self->_userDB->setSessionInfo($req) ); return $self->_userDB->setSessionInfo($req);
PE_OK; PE_OK;
} }
sub setMacros { sub setMacros {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
foreach ( sort keys %{ $self->_macros } ) { foreach ( sort keys %{ $self->_macros } ) {
$req->{sessionInfo}->{$_} = $self->_macros->{$_}->($req); $req->{sessionInfo}->{$_} = $self->_macros->{$_}->($req);
} }
PE_OK; PE_OK;
} }
sub setGroups { sub setGroups {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->_userDB->setGroups(@_); return $self->_userDB->setGroups(@_);
} }
sub setPersistentSessionInfo { sub setPersistentSessionInfo {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
# Do not restore infos if session already opened # Do not restore infos if session already opened
unless ( $req->{id} ) { unless ( $req->{id} ) {
my $key = $req->{sessionInfo}->{ $self->conf->{whatToTrace} }; my $key = $req->{sessionInfo}->{ $self->conf->{whatToTrace} };
return PE_OK unless ( $key and length($key) ); return PE_OK unless ( $key and length($key) );
my $persistentSession = $self->getPersistentSession($key); my $persistentSession = $self->getPersistentSession($key);
if ($persistentSession) { if ($persistentSession) {
$self->lmLog( "Persistent session found for $key", 'debug' ); $self->lmLog( "Persistent session found for $key", 'debug' );
foreach my $k ( keys %{ $persistentSession->data } ) { foreach my $k ( keys %{ $persistentSession->data } ) {
# Do not restore some parameters # Do not restore some parameters
next if $k =~ /^_(?:utime|session_(?:u?id|kind))$/; next if $k =~ /^_(?:utime|session_(?:u?id|kind))$/;
$self->lmLog( "Restore persistent parameter $k", 'debug' ); $self->lmLog( "Restore persistent parameter $k", 'debug' );
$req->{sessionInfo}->{$k} = $persistentSession->data->{$k}; $req->{sessionInfo}->{$k} = $persistentSession->data->{$k};
} }
} }
} }
PE_OK; PE_OK;
} }
sub setLocalGroups { sub setLocalGroups {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
foreach ( sort keys %{ $self->_groups } ) { foreach ( sort keys %{ $self->_groups } ) {
if ( $self->_groups->{$_}->($req) ) ) if ( $self->_groups->{$_}->($req) ) {
{ $req->{sessionInfo}->{groups} .=
$req->{sessionInfo}->{groups} .= $self->conf->{multiValuesSeparator} . $_;
$self->conf->{multiValuesSeparator} . $_; $req->{sessionInfo}->{hGroups}->{$_}->{name} = $_;
$req->{sessionInfo}->{hGroups}->{$_}->{name} = $_; }
} }
}
# Clear values separator at the beginning # Clear values separator at the beginning
if ( $req->{sessionInfo}->{groups} ) { if ( $req->{sessionInfo}->{groups} ) {
$req->{sessionInfo}->{groups} =~ $req->{sessionInfo}->{groups} =~
s/^\Q$self->conf->{multiValuesSeparator}\E//o; s/^\Q$self->conf->{multiValuesSeparator}\E//o;
} }
PE_OK; PE_OK;
} }
sub store { sub store {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
# Now, user is authenticated => inform handler # Now, user is authenticated => inform handler
$req->userData( $req->sessionInfo ); $req->userData( $req->sessionInfo );
# Create second session for unsecure cookie # Create second session for unsecure cookie
if ( $self->conf->{securedCookie} == 2 ) { if ( $self->conf->{securedCookie} == 2 ) {
my $session2 = $self->getApacheSession( undef, 1 ); my $session2 = $self->getApacheSession( undef, 1 );
my %infos = %{ $req->{sessionInfo} }; my %infos = %{ $req->{sessionInfo} };
$infos{_httpSessionType} = 1; $infos{_httpSessionType} = 1;
$session2->update( \%infos ); $session2->update( \%infos );
$req->{sessionInfo}->{_httpSession} = $session2->id; $req->{sessionInfo}->{_httpSession} = $session2->id;
} }
# Main session # Main session
my $session = $self->getApacheSession( $req->{id}, 0, $self->{force} ); my $session = $self->getApacheSession( $req->{id}, 0, $self->{force} );
return PE_APACHESESSIONERROR unless ($session); return PE_APACHESESSIONERROR unless ($session);
# Compute unsecure cookie value if needed # Compute unsecure cookie value if needed
if ( $self->conf->{securedCookie} == 3 ) { if ( $self->conf->{securedCookie} == 3 ) {
$req->{sessionInfo}->{_httpSession} = $req->{sessionInfo}->{_httpSession} =
$self->conf->{cipher}->encryptHex( $self->{id}, "http" ); $self->conf->{cipher}->encryptHex( $self->{id}, "http" );
} }
# Fill session # Fill session
my $infos = {}; my $infos = {};
foreach my $k ( keys %{ $req->{sessionInfo} } ) { foreach my $k ( keys %{ $req->{sessionInfo} } ) {
next unless defined $req->{sessionInfo}->{$k}; next unless defined $req->{sessionInfo}->{$k};
my $displayValue = $req->{sessionInfo}->{$k}; my $displayValue = $req->{sessionInfo}->{$k};
if ( $self->conf->{hiddenAttributes} =~ /\b$k\b/ ) { if ( $self->conf->{hiddenAttributes} =~ /\b$k\b/ ) {
$displayValue = '****'; $displayValue = '****';
} }
$self->lmLog( "Store $displayValue in session key $k", 'debug' ); $self->lmLog( "Store $displayValue in session key $k", 'debug' );
$self->_dump($displayValue) if ref($displayValue); $self->_dump($displayValue) if ref($displayValue);
$infos->{$k} = $self->{sessionInfo}->{$k}; $infos->{$k} = $self->{sessionInfo}->{$k};
} }
$session->update($infos); $session->update($infos);
PE_OK; PE_OK;
} }
sub buildCookie { sub buildCookie {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
push @{ $req->respCookies }, $self->cookie( push @{ $req->respCookies },
name => $self->{cookieName}, $self->cookie(
value => $self->{id}, name => $self->{cookieName},
domain => $self->{domain}, value => $self->{id},
path => "/", domain => $self->{domain},
secure => $self->{securedCookie}, path => "/",
HttpOnly => $self->{httpOnly}, secure => $self->{securedCookie},
expires => $self->{cookieExpiration}, HttpOnly => $self->{httpOnly},
@_, expires => $self->{cookieExpiration},
); @_,
if ( $self->conf->{securedCookie} >= 2 ) { );
push @{ $req->respCookies }, if ( $self->conf->{securedCookie} >= 2 ) {
$self->cookie( push @{ $req->respCookies },
name => $self->{cookieName} . "http", $self->cookie(
value => $self->{sessionInfo}->{_httpSession}, name => $self->{cookieName} . "http",
domain => $self->{domain}, value => $self->{sessionInfo}->{_httpSession},
path => "/", domain => $self->{domain},
secure => 0, path => "/",
HttpOnly => $self->{httpOnly}, secure => 0,
expires => $self->{cookieExpiration}, HttpOnly => $self->{httpOnly},
@_, expires => $self->{cookieExpiration},
); @_,
} );
PE_OK; }
PE_OK;
} }
sub cookie { sub cookie {
my ( $self, %h ) = @_; my ( $self, %h ) = @_;
my @res; my @res;
$req[0] = "$h{name}" or die("name required"); $res[0] = "$h{name}" or die("name required");
my $res[0] .= "=$h{value}"; $res[0] .= "=$h{value}";
foreach (qw(domain path expires max_age)) { foreach (qw(domain path expires max_age)) {
my $f = $_; my $f = $_;
s/_/-/g; s/_/-/g;
push @res, "$_=$h{$f}" if ( $h{$f} ); push @res, "$_=$h{$f}" if ( $h{$f} );
} }
return join( '; ', @res ); return join( '; ', @res );
} }
1; 1;

View File

@ -1,15 +1,18 @@
package Lemonldap::NG::Portal::Main::Request; package Lemonldap::NG::Portal::Main::Request;
# Developpers, be careful: new() is never called so default values will not be
# taken in account (see Portal::Run::handler())
use strict; use strict;
use Mouse; use Mouse;
extends 'Lemonldap::NG::Common::PSGI::Request'; extends 'Lemonldap::NG::Common::PSGI::Request';
# List of methods to call # List of methods to call
has steps => ( is => 'rw' ); has steps => ( is => 'rw' );
# Datas shared between methods # Datas shared between methods
has datas => ( is => 'rw', default => sub { {} } ); has datas => ( is => 'rw', default => sub { {} } );
# Session datas when created # Session datas when created
has id => ( is => 'rw' ); has id => ( is => 'rw' );
@ -19,7 +22,13 @@ has sessionInfo => ( is => 'rw' );
has respCookies => ( is => 'rw' ); has respCookies => ( is => 'rw' );
# Template to display (if not defined, login or menu) # Template to display (if not defined, login or menu)
has template => ( is => 'rw' ); has template => ( is => 'rw' );
# Boolean to indicate that response must be a redirection
has mustRedirect => ( is => 'rw' );
# Boolean to indicate that url isn't Base64 encoded
has urlNotBase64 => ( is => 'rw' );
sub wantJSON { sub wantJSON {
return $_[0]->accept =~ m#(?:application|text)/json# ? 1 : 0; return $_[0]->accept =~ m#(?:application|text)/json# ? 1 : 0;

View File

@ -1,18 +1,12 @@
##@class Lemonldap::NG::Portal::Main::Run ##@class Lemonldap::NG::Portal::Main::Run
# Serve request part of Lemonldap::NG portal # Serve request part of Lemonldap::NG portal
# #
# Methods: # Parts of this file:
# - handler(): verify that portal configuration is the same that the # - response handler
# underlying handler configuration before launching # - main entry points
# Lemonldap::NG::Common::PSGI::Router::handler() (which parse # - running methods
# routes) # - utilities
# #
# Entry points:
# - "/test": - authenticated() for already authenticated users
# - pleaseAuth() for others
# - "/": - login() ~first access
# - postLogin(), same for POST requests
# - authenticatedRequest() for authenticated users
package Lemonldap::NG::Portal::Main::Run; package Lemonldap::NG::Portal::Main::Run;
use strict; use strict;
@ -22,6 +16,21 @@ use Lemonldap::NG::Portal::Main::Request;
our $VERSION = '2.0.0'; our $VERSION = '2.0.0';
# List constants
sub authProcess { qw(extractFormInfo getUser authenticate) }
sub sessionDatas {
qw(setSessionInfo setMacros setGroups setPersistentSessionInfo
setLocalGroups store buildCookie);
}
# RESPONSE HANDLER
# ----------------
#
# - check if conf has changed
# - replace Lemonldap::NG::Common::PSGI::Request request by
# Lemonldap::NG::Portal::Main::Request
# - launch Lemonldap::NG::Common::PSGI::Request::handler()
sub handler { sub handler {
my ( $self, $req ) = shift; my ( $self, $req ) = shift;
unless ($self->conf->{cfgNum} unless ($self->conf->{cfgNum}
@ -33,9 +42,16 @@ sub handler {
return $self->SUPER::handler($req); return $self->SUPER::handler($req);
} }
# CORE REST API # MAIN ENTRY POINTS (declared in Lemonldap::NG::Portal::Main::Init)
# -----------------
#
# Entry points:
# - "/test": - authenticated() for already authenticated users
# - pleaseAuth() for others
# - "/": - login() ~first access
# - postLogin(), same for POST requests
# - authenticatedRequest() for authenticated users
# Methods that handle /test
sub authenticated { sub authenticated {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
return $self->sendJSONresponse( $req, { status => 1 } ); return $self->sendJSONresponse( $req, { status => 1 } );
@ -46,15 +62,6 @@ sub pleaseAuth {
return $self->sendJSONresponse( $req, { status => 0 } ); return $self->sendJSONresponse( $req, { status => 0 } );
} }
# MAIN ENTRY POINTS
# List constants
sub authProcess { qw(extractFormInfo getUser authenticate) }
sub sessionDatas {
qw(setSessionInfo setMacros setGroups setPersistentSessionInfo
setLocalGroups store buildCookie);
}
sub login { sub login {
my ( $self, $req ) = @_; my ( $self, $req ) = @_;
@ -73,9 +80,10 @@ sub postLogin {
return $req->do( return $req->do(
$req, $req,
[ [
'restoreArgs', 'controlUrl' @{ $self->beforeAuth }, 'restoreArgs', 'controlUrl',
&authProcess, @{ $self->betweenAuthAndDatas }, @{ $self->beforeAuth }, &authProcess,
&sessionDatas, @{ $self->afterdatas }, @{ $self->betweenAuthAndDatas }, &sessionDatas,
@{ $self->afterdatas },
] ]
); );
} }
@ -85,6 +93,9 @@ sub authenticatedRequest {
return $req->do( $req, $self->forAuthUser ); return $req->do( $req, $self->forAuthUser );
} }
# RUNNING METHODS
# ---------------
sub do { sub do {
my ( $self, $req, $steps ) = @_; my ( $self, $req, $steps ) = @_;
$req->steps($steps); $req->steps($steps);
@ -117,18 +128,32 @@ sub do {
} }
} }
sub process { # Utilities
my ( $self, $req ) = @_; # ---------
#$req->error(PE_OK); sub getModule {
my $err = PE_OK; my ( $self, $req, $type ) = @_;
while ( my $sub = shift @{ $req->steps } ) { if (
last if ( $err = $self->$sub($req) ); my $mod = {
auth => '_authentication',
user => '_userDB',
password => '_passwordDB'
}->{$type}
)
{
if ( $self->$mod->can('name') ) {
return $self->$mod->can('name');
}
else {
return ref( $self->$mod );
}
}
elsif ( $type eq 'issuer' ) {
return $req->{_activeIssuerDB};
}
else {
die "Unknown type $type";
} }
return $err;
} }
# TODO in run
# - mustRedirect
1; 1;