Commit 2e59ea44 authored by Yadd's avatar Yadd

Replace request management in handler (#1044)

Note: this is a big change, more tests needed
parent cc1fc22d
...@@ -29,11 +29,14 @@ sub new { ...@@ -29,11 +29,14 @@ sub new {
$self->env->{PATH_INFO} =~ s|^$tmp|/|; $self->env->{PATH_INFO} =~ s|^$tmp|/|;
$self->{uri} = uri_unescape( $self->env->{REQUEST_URI} ); $self->{uri} = uri_unescape( $self->env->{REQUEST_URI} );
$self->{uri} =~ s|//+|/|g; $self->{uri} =~ s|//+|/|g;
$self->{datas} = {};
$self->{error} = 0; $self->{error} = 0;
$self->{respHeaders} = []; $self->{respHeaders} = [];
return $self; return bless( $self, $_[0] );
} }
sub datas { $_[0]->{datas} }
sub uri { $_[0]->{uri} } sub uri { $_[0]->{uri} }
sub userData { sub userData {
......
...@@ -9,6 +9,7 @@ lib/Lemonldap/NG/Handler/ApacheMP2/CDA.pm ...@@ -9,6 +9,7 @@ lib/Lemonldap/NG/Handler/ApacheMP2/CDA.pm
lib/Lemonldap/NG/Handler/ApacheMP2/DevOps.pm lib/Lemonldap/NG/Handler/ApacheMP2/DevOps.pm
lib/Lemonldap/NG/Handler/ApacheMP2/Main.pm lib/Lemonldap/NG/Handler/ApacheMP2/Main.pm
lib/Lemonldap/NG/Handler/ApacheMP2/Menu.pm lib/Lemonldap/NG/Handler/ApacheMP2/Menu.pm
lib/Lemonldap/NG/Handler/ApacheMP2/Request.pm
lib/Lemonldap/NG/Handler/ApacheMP2/SecureToken.pm lib/Lemonldap/NG/Handler/ApacheMP2/SecureToken.pm
lib/Lemonldap/NG/Handler/ApacheMP2/ServiceToken.pm lib/Lemonldap/NG/Handler/ApacheMP2/ServiceToken.pm
lib/Lemonldap/NG/Handler/ApacheMP2/ZimbraPreAuth.pm lib/Lemonldap/NG/Handler/ApacheMP2/ZimbraPreAuth.pm
...@@ -48,7 +49,6 @@ META.yml ...@@ -48,7 +49,6 @@ META.yml
README README
t/01-Lemonldap-NG-Handler-Main.t t/01-Lemonldap-NG-Handler-Main.t
t/05-Lemonldap-NG-Handler-Reload.t t/05-Lemonldap-NG-Handler-Reload.t
t/10-Lemonldap-NG-Handler-SharedConf.t
t/12-Lemonldap-NG-Handler-Jail.t t/12-Lemonldap-NG-Handler-Jail.t
t/13-Lemonldap-NG-Handler-Fake-Safe.t t/13-Lemonldap-NG-Handler-Fake-Safe.t
t/50-Lemonldap-NG-Handler-SecureToken.t t/50-Lemonldap-NG-Handler-SecureToken.t
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
package Lemonldap::NG::Handler::ApacheMP2; package Lemonldap::NG::Handler::ApacheMP2;
use strict; use strict;
use Lemonldap::NG::Handler::ApacheMP2::Request;
use Lemonldap::NG::Handler::ApacheMP2::Main; use Lemonldap::NG::Handler::ApacheMP2::Main;
...@@ -13,30 +14,31 @@ our $VERSION = '2.0.0'; ...@@ -13,30 +14,31 @@ our $VERSION = '2.0.0';
sub handler { sub handler {
shift if ($#_); shift if ($#_);
my ($res) = getClass(@_)->run(@_); return launch( 'run', @_ );
return $res;
} }
sub logout { sub logout {
shift if ($#_); shift if ($#_);
return getClass(@_)->unlog(@_); return launch( 'unlog', @_ );
} }
sub status { sub status {
shift if ($#_); shift if ($#_);
return getClass(@_)->getStatus(@_); return launch( 'getStatus', @_ );
} }
# Internal method to get class to load # Internal method to get class to load
sub getClass { sub launch {
my $type = Lemonldap::NG::Handler::ApacheMP2::Main->checkType(@_); my ( $sub, $r ) = @_;
my $req = Lemonldap::NG::Handler::Apache2::Request->new($r);
my $type = Lemonldap::NG::Handler::ApacheMP2::Main->checkType($req);
if ( my $t = $_[0]->dir_config('VHOSTTYPE') ) { if ( my $t = $_[0]->dir_config('VHOSTTYPE') ) {
$type = $t; $type = $t;
} }
my $class = "Lemonldap::NG::Handler::ApacheMP2::$type"; my $class = "Lemonldap::NG::Handler::ApacheMP2::$type";
eval "require $class"; eval "require $class";
die $@ if ($@); die $@ if ($@);
return $class; return $class->$sub($req);
} }
1; 1;
...@@ -43,33 +43,6 @@ our $request; # Apache2::RequestRec object for current request ...@@ -43,33 +43,6 @@ our $request; # Apache2::RequestRec object for current request
#*run = \&Lemonldap::NG::Handler::Main::run; #*run = \&Lemonldap::NG::Handler::Main::run;
## @rmethod protected int redirectFilter(string url, Apache2::Filter f)
# Launch the current HTTP request then redirects the user to $url.
# Used by logout_app and logout_app_sso targets
# @param $url URL to redirect the user
# @param $f Current Apache2::Filter object
# @return Constant $class->OK
sub redirectFilter {
my $class = shift;
my $url = shift;
my $f = shift;
unless ( $f->ctx ) {
# Here, we can use Apache2 functions instead of set_header_out
# since this function is used only with Apache2.
$f->r->status( $class->REDIRECT );
$f->r->status_line("303 See Other");
$f->r->headers_out->unset('Location');
$f->r->err_headers_out->set( 'Location' => $url );
$f->ctx(1);
}
while ( $f->read( my $buffer, 1024 ) ) {
}
$class->updateStatus( $f->r, '$class->REDIRECT',
$class->datas->{ $class->tsv->{whatToTrace} }, 'filter' );
return $class->OK;
}
__PACKAGE__->init(); __PACKAGE__->init();
# INTERNAL METHODS # INTERNAL METHODS
...@@ -99,36 +72,21 @@ sub setServerSignature { ...@@ -99,36 +72,21 @@ sub setServerSignature {
}; };
} }
sub newRequest {
my ( $class, $r ) = @_;
$request = $r;
}
## @method void set_user(string user) ## @method void set_user(string user)
# sets remote_user # sets remote_user
# @param user string username # @param user string username
sub set_user { sub set_user {
my ( $class, $user ) = @_; my ( $class, $request, $user ) = @_;
$request->user($user); $request->env->{'psgi.r'}->user($user);
}
## @method string header_in(string header)
# returns request header value
# @param header string request header
# @return request header value
sub header_in {
my ( $class, $header ) = @_;
$header ||= $class; # to use header_in as a method or as a function
return $request->headers_in->{$header};
} }
## @method void set_header_in(hash headers) ## @method void set_header_in(hash headers)
# sets or modifies request headers # sets or modifies request headers
# @param headers hash containing header names => header value # @param headers hash containing header names => header value
sub set_header_in { sub set_header_in {
my ( $class, %headers ) = @_; my ( $class, $request, %headers ) = @_;
while ( my ( $h, $v ) = each %headers ) { while ( my ( $h, $v ) = each %headers ) {
$request->headers_in->set( $h => $v ); $request->env->{'psgi.r'}->headers_in->set( $h => $v );
} }
} }
...@@ -138,11 +96,11 @@ sub set_header_in { ...@@ -138,11 +96,11 @@ sub set_header_in {
# header 'Auth-User' is removed, 'Auth_User' be removed also # header 'Auth-User' is removed, 'Auth_User' be removed also
# @param headers array with header names to remove # @param headers array with header names to remove
sub unset_header_in { sub unset_header_in {
my ( $class, @headers ) = @_; my ( $class, $request, @headers ) = @_;
foreach my $h1 (@headers) { foreach my $h1 (@headers) {
$h1 = lc $h1; $h1 = lc $h1;
$h1 =~ s/-/_/g; $h1 =~ s/-/_/g;
$request->headers_in->do( $request->env->{'psgi.r'}->headers_in->do(
sub { sub {
my $h = shift; my $h = shift;
my $h2 = lc $h; my $h2 = lc $h;
...@@ -158,120 +116,65 @@ sub unset_header_in { ...@@ -158,120 +116,65 @@ sub unset_header_in {
# sets response headers # sets response headers
# @param headers hash containing header names => header value # @param headers hash containing header names => header value
sub set_header_out { sub set_header_out {
my ( $class, %headers ) = @_; my ( $class, $request, %headers ) = @_;
while ( my ( $h, $v ) = each %headers ) { while ( my ( $h, $v ) = each %headers ) {
$request->err_headers_out->set( $h => $v ); $request->env->{'psgi.r'}->err_headers_out->set( $h => $v );
} }
} }
## @method string hostname()
# returns host, as set by full URI or Host header
# @return host string Host value
sub hostname {
my $class = shift;
return $request->hostname;
}
## @method string remote_ip
# returns client IP address
# @return IP_Addr string client IP
sub remote_ip {
my $class = shift;
my $remote_ip = (
$request->connection->can('remote_ip')
? $request->connection->remote_ip
: $request->connection->client_ip
);
return $remote_ip;
}
## @method boolean is_initial_req ## @method boolean is_initial_req
# returns true unless the current request is a subrequest # returns true unless the current request is a subrequest
# @return is_initial_req boolean # @return is_initial_req boolean
sub is_initial_req { sub is_initial_req {
my $class = shift; return $_[1]->env->{'psgi.r'}->is_initial_req;
return $request->is_initial_req;
}
## @method string args(string args)
# gets the query string
# @return args string Query string
sub args {
my $class = shift;
return $request->args();
}
## @method string uri
# returns the path portion of the URI, normalized, i.e. :
# * URL decoded (characters encoded as %XX are decoded,
# except ? in order not to merge path and query string)
# * references to relative path components "." and ".." are resolved
# * two or more adjacent slashes are merged into a single slash
# @return path portion of the URI, normalized
sub uri {
my $class = shift;
my $uri = $request->uri;
$uri =~ s#//+#/#g;
$uri =~ s#\?#%3F#g;
return $uri;
}
## @method string uri_with_args
# returns the URI, with arguments and with path portion normalized
# @return URI with normalized path portion
sub uri_with_args {
my $class = shift;
return uri . ( $request->args ? "?" . $request->args : "" );
}
## @method string unparsed_uri
# returns the full original request URI, with arguments
# @return full original request URI, with arguments
sub unparsed_uri {
my $class = shift;
return $request->unparsed_uri;
}
## @method string get_server_port
# returns the port the server is receiving the current request on
# @return port string server port
sub get_server_port {
my $class = shift;
return $request->get_server_port;
}
## @method string method
# returns the port the server is receiving the current request on
# @return port string server port
sub method {
my $class = shift;
return $request->method;
}
## Return environment variables as hash
sub env {
return \%ENV;
} }
## @method void print(string data) ## @method void print(string data)
# write data in HTTP response body # write data in HTTP response body
# @param data Text to add in response body # @param data Text to add in response body
sub print { sub print {
my ( $class, $data ) = @_; my ( $class, $request, $data ) = @_;
$request->print($data); $request->env->{'psgi.r'}->print($data);
} }
1; 1;
__END__ __END__
## @rmethod protected int redirectFilter(string url, Apache2::Filter f)
# Launch the current HTTP request then redirects the user to $url.
# Used by logout_app and logout_app_sso targets
# @param $url URL to redirect the user
# @param $f Current Apache2::Filter object
# @return Constant $class->OK
sub redirectFilter {
my $class = shift;
my $url = shift;
my $f = shift;
unless ( $f->ctx ) {
# Here, we can use Apache2 functions instead of set_header_out
# since this function is used only with Apache2.
$f->r->status( $class->REDIRECT );
$f->r->status_line("303 See Other");
$f->r->headers_out->unset('Location');
$f->r->err_headers_out->set( 'Location' => $url );
$f->ctx(1);
}
while ( $f->read( my $buffer, 1024 ) ) {
}
$class->updateStatus( $f->r, '$class->REDIRECT',
$class->datas->{ $class->tsv->{whatToTrace} }, 'filter' );
return $class->OK;
}
## @method void addToHtmlHead(string data) ## @method void addToHtmlHead(string data)
# add data at end of html head # add data at end of html head
# @param data Text to add in html head # @param data Text to add in html head
sub addToHtmlHead { sub addToHtmlHead {
use APR::Bucket (); use APR::Bucket ();
use APR::Brigade (); use APR::Brigade ();
my ( $class, $data ) = @_; my ( $class, $request, $data ) = @_;
$request->add_output_filter( $request->add_output_filter(
sub { sub {
my $f = shift; my $f = shift;
...@@ -322,7 +225,7 @@ sub flatten_bb { ...@@ -322,7 +225,7 @@ sub flatten_bb {
# add or modify parameters in POST request body # add or modify parameters in POST request body
# @param $params hashref containing name => value # @param $params hashref containing name => value
sub setPostParams { sub setPostParams {
my ( $class, $params ) = @_; my ( $class, $request, $params ) = @_;
$request->add_input_filter( $request->add_input_filter(
sub { sub {
my $f = shift; my $f = shift;
......
package Lemonldap::NG::Handler::ApacheMP2::Request;
use strict;
use base 'Plack::Request';
use Plack::Util;
use URI;
use URI::Escape;
# Build Plack::Request (inspired from Plack::Handler::Apache2)
sub new {
my ( $class, $r ) = @_;
# Apache populates ENV:
$r->subprocess_env;
my $env = {
%ENV,
'psgi.version' => [ 1, 1 ],
'psgi.url_scheme' => ( $ENV{HTTPS} || 'off' ) =~ /^(?:on|1)$/i
? 'https'
: 'http',
'psgi.input' => $r,
'psgi.errors' => *STDERR,
'psgi.multithread' => Plack::Util::FALSE,
'psgi.multiprocess' => Plack::Util::TRUE,
'psgi.run_once' => Plack::Util::FALSE,
'psgi.streaming' => Plack::Util::TRUE,
'psgi.nonblocking' => Plack::Util::FALSE,
'psgix.harakiri' => Plack::Util::TRUE,
'psgix.cleanup' => Plack::Util::TRUE,
'psgix.cleanup.handlers' => [],
'psqi.r' => $r,
};
if ( defined( my $HTTP_AUTHORIZATION = $r->headers_in->{Authorization} ) ) {
$env->{HTTP_AUTHORIZATION} = $HTTP_AUTHORIZATION;
}
my $uri = URI->new( "http://" . $r->hostname . $r->{env}->{REQUEST_URI} );
$env->{PATH_INFO} = uri_unescape( $uri->path );
my $self = Plack::Request->new($env);
bless $self, $class;
return $self;
}
sub datas {
my($self) = @_;
return $self->{datas} ||= {};
}
1;
...@@ -24,8 +24,8 @@ our @EXPORT_OK = @EXPORT; ...@@ -24,8 +24,8 @@ our @EXPORT_OK = @EXPORT;
# using indefinitely a session id disclosed accidentally or maliciously. # using indefinitely a session id disclosed accidentally or maliciously.
# @return session id # @return session id
sub fetchId { sub fetchId {
my $class = shift; my ( $class, $req ) = @_;
if ( my $creds = $class->header_in('Authorization') ) { if ( my $creds = $req->env->{'HTTP_AUTHORIZATION'} ) {
$creds =~ s/^Basic\s+//; $creds =~ s/^Basic\s+//;
my @date = localtime; my @date = localtime;
my $day = $date[5] * 366 + $date[7]; my $day = $date[5] * 366 + $date[7];
...@@ -41,17 +41,19 @@ sub fetchId { ...@@ -41,17 +41,19 @@ sub fetchId {
# and if needed, ask portal to create it through a SOAP request # and if needed, ask portal to create it through a SOAP request
# @return true if the session was found, false else # @return true if the session was found, false else
sub retrieveSession { sub retrieveSession {
my ( $class, $id ) = @_; my ( $class, $req, $id ) = @_;
# First check if session already exists # First check if session already exists
if ( my $res = $class->Lemonldap::NG::Handler::Main::retrieveSession($id) ) if ( my $res =
$class->Lemonldap::NG::Handler::Main::retrieveSession( $req, $id ) )
{ {
return $res; return $res;
} }
# Then ask portal to create it # Then ask portal to create it
if ( $class->createSession($id) ) { if ( $class->createSession( $req, $id ) ) {
return $class->Lemonldap::NG::Handler::Main::retrieveSession($id); return $class->Lemonldap::NG::Handler::Main::retrieveSession( $req,
$id );
} }
else { else {
return 0; return 0;
...@@ -62,12 +64,12 @@ sub retrieveSession { ...@@ -62,12 +64,12 @@ sub retrieveSession {
# Ask portal to create it through a SOAP request # Ask portal to create it through a SOAP request
# @return true if the session is created, else false # @return true if the session is created, else false
sub createSession { sub createSession {
my ( $class, $id ) = @_; my ( $class, $req, $id ) = @_;
# Add client IP as X-Forwarded-For IP in SOAP request # Add client IP as X-Forwarded-For IP in SOAP request
my $xheader = $class->header_in('X-Forwarded-For'); my $xheader = $req->env->{'HTTP_X_FORWARDED_FOR'};
$xheader .= ", " if ($xheader); $xheader .= ", " if ($xheader);
$xheader .= $class->remote_ip; $xheader .= $req->{env}->{REMOTE_ADDR};
#my $soapHeaders = HTTP::Headers->new( "X-Forwarded-For" => $xheader ); #my $soapHeaders = HTTP::Headers->new( "X-Forwarded-For" => $xheader );
## TODO: use adminSession or sessions ## TODO: use adminSession or sessions
...@@ -76,7 +78,7 @@ sub createSession { ...@@ -76,7 +78,7 @@ sub createSession {
# default_headers => $soapHeaders # default_headers => $soapHeaders
#)->uri('urn:Lemonldap/NG/Common/PSGI/SOAPService'); #)->uri('urn:Lemonldap/NG/Common/PSGI/SOAPService');
my $creds = $class->header_in('Authorization'); my $creds = $req->env->{'HTTP_AUTHORIZATION'};
$creds =~ s/^Basic\s+//; $creds =~ s/^Basic\s+//;
my ( $user, $pwd ) = ( decode_base64($creds) =~ /^(.*?):(.*)$/ ); my ( $user, $pwd ) = ( decode_base64($creds) =~ /^(.*?):(.*)$/ );
$class->logger->debug("AuthBasic authentication for user: $user"); $class->logger->debug("AuthBasic authentication for user: $user");
...@@ -84,18 +86,18 @@ sub createSession { ...@@ -84,18 +86,18 @@ sub createSession {
#my $soapRequest = $soapClient->getCookies( $user, $pwd, $id ); #my $soapRequest = $soapClient->getCookies( $user, $pwd, $id );
my $url = $class->tsv->{portal}->() . "/sessions/global/$id?auth"; my $url = $class->tsv->{portal}->() . "/sessions/global/$id?auth";
$url =~ s#//sessions/#/sessions/#g; $url =~ s#//sessions/#/sessions/#g;
my $req = HTTP::Request->new( POST => $url ); my $get = HTTP::Request->new( POST => $url );
$req->header( 'X-Forwarded-For' => $xheader ); $get->header( 'X-Forwarded-For' => $xheader );
$req->header( 'Content-Type' => 'application/x-www-form-urlencoded' ); $get->header( 'Content-Type' => 'application/x-www-form-urlencoded' );
$req->header( Accept => 'application/json' ); $get->header( Accept => 'application/json' );
$req->content( $get->content(
build_urlencoded( build_urlencoded(
user => $user, user => $user,
password => $pwd, password => $pwd,
secret => $class->tsv->{cipher}->encrypt(time)