Rearrange Nginx files

This commit is contained in:
Xavier Guimard 2016-01-30 12:26:14 +00:00
parent 9237dc785a
commit aa34a28bd3
13 changed files with 341 additions and 287 deletions

View File

@ -321,6 +321,11 @@ start_web_server: all prepare_test_server
@if test "$(TESTWEBSERVER)" = "apache"; then \
LLNG_DEFAULTCONFFILE=`pwd`/e2e-tests/conf/lemonldap-ng.ini /usr/sbin/apache2 -d `pwd`/e2e-tests -f apache2.conf -k start; \
elif test "$(TESTWEBSERVER)" = "nginx"; then \
echo "Testing nginx conf"; \
$(NGINX) -t -p `pwd`/e2e-tests \
-g 'error_log '`pwd`'/e2e-tests/conf/nginx.log;' \
-c `pwd`/e2e-tests/nginx.conf \
2>&1 | grep -v 'Permission denied' || true; \
echo "Launching nginx"; \
$(NGINX) -p `pwd`/e2e-tests \
-g 'error_log '`pwd`'/e2e-tests/conf/nginx.log;' \
@ -364,12 +369,10 @@ restart_web_server: start_web_server
plackup:
@LLNG_DEFAULTCONFFILE=`pwd`/e2e-tests/conf/lemonldap-ng.ini \
/sbin/start-stop-daemon --start \
--pidfile e2e-tests/conf/plackup.pid \
-d `pwd` -b -m \
--exec /usr/bin/plackup -- \
-s FCGI \
plackup -s FCGI \
--listen e2e-tests/conf/llng.sock \
--daemonize --pid e2e-tests/conf/plackup.pid \
--nproc 1 --proc-title llng-fastcgi-server \
e2e-tests/llng.psgi
#

View File

@ -25,7 +25,8 @@ server {
# Client requests
location / {
auth_request /lmauth;
auth_request_set $lmlocation $upstream_http_x_location;
auth_request_set $lmremote_user $upstream_http_lm_remote_user;
auth_request_set $lmlocation $upstream_http_location;
error_page 401 $lmlocation;
try_files $uri $uri/ =404;

2
debian/control vendored
View File

@ -111,7 +111,7 @@ Package: liblemonldap-ng-handler-perl
Architecture: all
Depends: ${misc:Depends},
${perl:Depends},
libapache2-mod-perl2,
libapache2-mod-perl2 | nginx,
liblemonldap-ng-common-perl (= ${binary:Version}),
libmouse-perl,
liburi-perl,

View File

@ -18,8 +18,8 @@ my %_apps;
my %builder = (
handler => sub {
require Lemonldap::NG::Handler::PSGI::Server;
return Lemonldap::NG::Handler::PSGI::Server->run( {} );
require Lemonldap::NG::Handler::Nginx;
return Lemonldap::NG::Handler::Nginx->run( {} );
},
manager => sub {
require Lemonldap::NG::Manager;

View File

@ -119,6 +119,8 @@ has CONTENT_LENGTH => (
);
has error => ( is => 'rw', isa => 'Str', default => '' );
has respHeaders => ( is => 'rw', isa => 'HashRef' );
# JSON parser
sub jsonBodyToObj {
my $self = shift;

View File

@ -43,8 +43,8 @@ sub AUTOLOAD {
# - Nginx
if ( !$mode or $func eq 'newRequest' ) {
$mode =
( ( caller(1) )[0] eq 'Lemonldap::NG::Handler::PSGI::Server' )
? 'PSGI/Server'
( ( caller(1) )[0] eq 'Lemonldap::NG::Handler::Nginx' )
? 'Nginx'
: (
( caller(0) )[0] =~ /^Lemonldap::NG::Handler::PSGI/
or (
@ -57,7 +57,7 @@ sub AUTOLOAD {
: $ENV{GATEWAY_INTERFACE} ? 'CGI'
: ( MP == 2 ) ? 'ApacheMP2'
: ( MP == 1 ) ? 'ApacheMP1'
: $main::{'nginx::'} ? 'Nginx'
: $main::{'nginx::'} ? 'ExperimentalNginx'
: 'CGI';
unless ( $INC{"Lemonldap/NG/Handler/API/$mode.pm"} ) {
$mode =~ s#/#::#g;

View File

@ -0,0 +1,222 @@
package Lemonldap::NG::Handler::API::Nginx;
our $VERSION = '1.9.0';
use constant FORBIDDEN => 403;
use constant HTTP_UNAUTHORIZED => 401;
use constant REDIRECT => 302;
use constant OK => 0;
use constant DECLINED => -1;
use constant DONE => -2;
use constant SERVER_ERROR => 500;
use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
my $request; # Nginx object for current request
## @method void thread_share(string $variable)
# not applicable with Nginx
sub thread_share {
}
## @method void setServerSignature(string sign)
# modifies web server signature
# @param $sign String to add to server signature
sub setServerSignature {
my ( $class, $sign ) = @_;
# TODO
}
sub newRequest {
my ( $class, $r ) = @_;
$request = $r;
$Lemonldap::NG::API::mode = 'Nginx';
}
## @method void lmLog(string $msg, string $level)
# logs message $msg to Apache logs with loglevel $level
# @param $msg string message to log
# @param $level string loglevel
sub lmLog {
my ( $class, $msg, $level ) = @_;
# TODO
}
## @method void set_user(string user)
# sets remote_user
# @param user string username
sub set_user {
my ( $class, $user ) = @_;
$request->variable( 'lmremote_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->header_in($header);
}
## @method void set_header_in(hash headers)
# sets or modifies request headers
# @param headers hash containing header names => header value
sub set_header_in {
my ( $class, %headers ) = @_;
while ( my ( $h, $v ) = each %headers ) {
if ( $h =~ /cookie/i ) {
# TODO: check that variable $lmcookie is defined,
# else warn that LL::NG cookie will not be removed
$request->variable( 'lmcookie', $v );
}
else {
# TODO: check that header is not yet set, else throw warning
# or reject request if mode paranoid is set
# TODO: check that variable nginxName($h) is defined,
# else warn that header will not be sent
$request->variable( nginxName($h), $v );
}
}
}
## @method void unset_header_in(array headers)
# removes request headers
# @param headers array with header names to remove
sub unset_header_in {
my ( $class, @headers ) = @_;
foreach my $h1 (@headers) {
# TODO: check that header is not yet set, else throw warning
$request->variable( nginxName($h), '' );
}
}
## @method void set_header_out(hash headers)
# sets response headers
# @param headers hash containing header names => header value
sub set_header_out {
my ( $class, %headers ) = @_;
while ( my ( $h, $v ) = each %headers ) {
if ( $h =~ /location/i ) {
$request->variable( 'lmlocation', $v );
}
else {
$request->header_out( $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->variable('host');
}
## @method string remote_ip
# returns client IP address
# @return IP_Addr string client IP
sub remote_ip {
my $class = shift;
return $request->variable('remote_addr');
}
## @method boolean is_initial_req
# returns true unless the current request is a subrequest
# @return is_initial_req boolean
sub is_initial_req {
my $class = shift;
return 1;
}
## @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;
return $request->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->variable('request_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->variable('server_port');
}
## @method string method
# returns the method the request is sent with
# @return port string server port
sub method {
my $class = shift;
return $request->request_method;
}
## @method void print(string data)
# write data in HTTP response body
# @param data Text to add in response body
sub print {
my ( $class, $data ) = @_;
$request->print($data);
}
## @method void addToHtmlHead(string data)
# add data at end of html head: not feasible with Nginx
# @param data Text to add in html head
sub addToHtmlHead {
my ( $class, $data ) = @_;
# TODO: throw error log
}
## @method void setPostParams(hashref $params)
# add or modify parameters in POST request body: not feasible with Nginx
# @param $params hashref containing name => value
sub setPostParams {
my ( $class, $params ) = @_;
# TODO: throw error log
}
sub nginxName {
my $h = lc(shift);
$h =~ s/-/_/g;
return "lm_$h";
}
1;

View File

@ -1,222 +1,12 @@
package Lemonldap::NG::Handler::API::Nginx;
use strict;
our $VERSION = '1.9.0';
use constant FORBIDDEN => 403;
use constant HTTP_UNAUTHORIZED => 401;
use constant REDIRECT => 302;
use constant OK => 0;
use constant DECLINED => -1;
use constant DONE => -2;
use constant SERVER_ERROR => 500;
use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
use base 'Lemonldap::NG::Handler::API::PSGI';
my $request; # Nginx object for current request
## @method void thread_share(string $variable)
# not applicable with Nginx
sub thread_share {
}
## @method void setServerSignature(string sign)
# modifies web server signature
# @param $sign String to add to server signature
sub setServerSignature {
my ( $class, $sign ) = @_;
# TODO
}
sub newRequest {
my ( $class, $r ) = @_;
$request = $r;
$Lemonldap::NG::API::mode = 'Nginx';
}
## @method void lmLog(string $msg, string $level)
# logs message $msg to Apache logs with loglevel $level
# @param $msg string message to log
# @param $level string loglevel
sub lmLog {
my ( $class, $msg, $level ) = @_;
# TODO
}
## @method void set_user(string user)
# sets remote_user
# @param user string username
sub set_user {
my ( $class, $user ) = @_;
$request->variable( 'lmremote_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->header_in($header);
}
## @method void set_header_in(hash headers)
# sets or modifies request headers
# @param headers hash containing header names => header value
sub set_header_in {
my ( $class, %headers ) = @_;
while ( my ( $h, $v ) = each %headers ) {
if ( $h =~ /cookie/i ) {
# TODO: check that variable $lmcookie is defined,
# else warn that LL::NG cookie will not be removed
$request->variable( 'lmcookie', $v );
}
else {
# TODO: check that header is not yet set, else throw warning
# or reject request if mode paranoid is set
# TODO: check that variable nginxName($h) is defined,
# else warn that header will not be sent
$request->variable( nginxName($h), $v );
}
}
}
## @method void unset_header_in(array headers)
# removes request headers
# @param headers array with header names to remove
sub unset_header_in {
my ( $class, @headers ) = @_;
foreach my $h1 (@headers) {
# TODO: check that header is not yet set, else throw warning
$request->variable( nginxName($h), '' );
}
}
## @method void set_header_out(hash headers)
# sets response headers
# @param headers hash containing header names => header value
sub set_header_out {
my ( $class, %headers ) = @_;
while ( my ( $h, $v ) = each %headers ) {
if ( $h =~ /location/i ) {
$request->variable( 'lmlocation', $v );
}
else {
$request->header_out( $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->variable('host');
}
## @method string remote_ip
# returns client IP address
# @return IP_Addr string client IP
sub remote_ip {
my $class = shift;
return $request->variable('remote_addr');
}
## @method boolean is_initial_req
# returns true unless the current request is a subrequest
# @return is_initial_req boolean
sub is_initial_req {
my $class = shift;
return 1;
}
## @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;
return $request->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->variable('request_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->variable('server_port');
}
## @method string method
# returns the method the request is sent with
# @return port string server port
sub method {
my $class = shift;
return $request->request_method;
}
## @method void print(string data)
# write data in HTTP response body
# @param data Text to add in response body
sub print {
my ( $class, $data ) = @_;
$request->print($data);
}
## @method void addToHtmlHead(string data)
# add data at end of html head: not feasible with Nginx
# @param data Text to add in html head
sub addToHtmlHead {
my ( $class, $data ) = @_;
# TODO: throw error log
}
## @method void setPostParams(hashref $params)
# add or modify parameters in POST request body: not feasible with Nginx
# @param $params hashref containing name => value
sub setPostParams {
my ( $class, $params ) = @_;
# TODO: throw error log
}
sub nginxName {
my $h = lc(shift);
$h =~ s/-/_/g;
return "lm_$h";
return $Lemonldap::NG::Handler::API::PSGI::request->original_uri;
}
1;

View File

@ -30,6 +30,10 @@ sub thread_share {
# nothing to do in PSGI
}
## @method void newRequest($r)
# Store request in global $request variable
#
#@param $r Lemonldap::NG::Common::PSGI::Request
sub newRequest {
my ( $class, $r ) = @_;
$request = $r;

View File

@ -1,12 +0,0 @@
package Lemonldap::NG::Handler::API::PSGI::Server;
use strict;
our $VERSION = '1.9.0';
use base 'Lemonldap::NG::Handler::API::PSGI';
sub uri {
return $Lemonldap::NG::Handler::API::PSGI::request->original_uri;
}
1;

View File

@ -0,0 +1,72 @@
# PSGI authentication package written for Nginx. It replace
# Lemonldap::NG::Handler::PSGI::Server to manage Nginx behaviour
package Lemonldap::NG::Handler::Nginx;
use strict;
use Mouse;
use Lemonldap::NG::Handler::SharedConf qw(:tsv);
extends 'Lemonldap::NG::Handler::PSGI';
## @method Code-Ref _run()
# Return a subroutine that call _authAndTrace() and tranform redirection
# response code from 302 to 401 (not authenticated) ones. This is required
# because Nginx "auth_request" parameter does not accept it. The Nginx
# configuration file should transform them back to 302 using:
#
# auth_request_set $lmlocation $upstream_http_location;
# error_page 401 $lmlocation;
#
#@return subroutine that will be called to manage FastCGI queries
sub _run {
my $self = shift;
return sub {
my $req = $_[0];
$self->lmLog( 'New request', 'debug' );
my $res = $self->_authAndTrace(
Lemonldap::NG::Common::PSGI::Request->new( $_[0] ) );
# Transform 302 responses in 401 since Nginx refuse it
if ( $res->[0] == 302 or $res->[0] == 303 ) {
$res->[0] = 401;
}
# TODO: transform headers in $res->[1]
return $res;
};
}
## @method PSGI-Response router()
# Transform headers returned by handler main process:
# each "Name: value" is transformed to:
# - Headername<i>: Name
# - Headervalue<i>: value
# where <i> is an integer starting from 1
# It can be used in Nginx virtualhost configuration:
#
# auth_request_set $headername1 $upstream_http_headername1;
# auth_request_set $headervalue1 $upstream_http_headervalue1;
# #proxy_set_header $headername1 $headervalue1;
# # OR
# #fastcgi_param $fheadername1 $headervalue1;
#
# It add also a header called Lm-Remote-User set to whatToTrace value that can
# be used in Nginx virtualhost configuration to insert user id in logs
#
# auth_request_set $llremoteuser $upstream_http_lm_remote_user
#
#@param $req Lemonldap::NG::Common::PSGI::Request
sub router {
my ( $self, $req ) = @_;
my $hdrs = $req->{respHeaders} || {};
my @convertedHdrs =
[ 'Lm-Remote-User', $self->userId, 'Content-Length', 0 ];
my $i = 0;
foreach my $k ( keys %$hdrs ) {
$i++;
push @convertedHdrs, "Headername$i", $k, "Headervalue$i", $hdrs->{$k};
}
return [ 200, \@convertedHdrs, [] ];
}
1;

View File

@ -8,6 +8,8 @@ our $VERSION = '1.9.0';
has protection => ( is => 'rw', isa => 'Str' );
## @method boolean init($args)
# Initalize main handler
sub init {
my ( $self, $args ) = @_;
eval { Lemonldap::NG::Handler::SharedConf->init($self) };
@ -25,6 +27,9 @@ sub init {
return 1;
}
## @methodi CODE-ref _run
# Check if protecton is activated then return a code ref that will launch
# _authAndTrace() if protection in on or router() else
sub _run {
my $self = shift;
@ -45,6 +50,8 @@ sub _run {
else {
$self->lmLog( 'PSGI app is not protected', 'debug' );
# Check if main handler initialization has been done
unless (%$tsv) {
$self->lmLog( 'Checking conf', 'debug' );
eval { Lemonldap::NG::Handler::SharedConf->checkConf() };
@ -58,6 +65,9 @@ sub _run {
}
}
## @method private PSGI-Response _authAndTrace($req)
# Launch Lemonldap::NG::Handler::SharedConf::run() and then router() if
# response is 200.
sub _authAndTrace {
my ( $self, $req ) = @_;
Lemonldap::NG::Handler::API->newRequest($req);
@ -69,36 +79,6 @@ sub _authAndTrace {
$self->lmLog( 'User authenticated, calling router()', 'debug' );
return $self->router($req);
}
# Ajax hook: Ajax requests can not understand 30x responses. This
# is not really HTTP compliant but nothing in this
# protocol can do this. Our javascripts understand that
# they have to prompt user with the URL
#elsif (
# $req->accept =~ m|application/json|
# or ( $req->contentType
# and $req->contentType =~ m|application/json| )
# )
#{
# $self->lmLog( 'Ajax request detected', 'debug' );
# if ( $res == 302 or $res == 303 ) {
# $self->lmLog( 'Rewrite redirection to 401 response', 'debug' );
# return [
# 401, [ 'WWW-Authenticate' => $req->{respHeaders}->{Location} ], ['']
# ];
# }
# else {
# $self->lmLog(
# "Lemonldap::NG::Handler::SharedConf::run() returns $res",
# 'debug' );
# return [
# $res, [ 'Content-Type', 'application/json' ],
# [qq({"error":"$res"})]
# ];
# }
#}
# Non Ajax requests may be redirected to portal
else {
my %h = $req->{respHeaders} ? %{ $req->{respHeaders} } : ();
my $s = $tsv->{portal}->() . "?lmError=$res";
@ -148,4 +128,5 @@ sub sendError {
$err = '[' . $self->userId($req) . "] $err";
return $self->Lemonldap::NG::Common::PSGI::sendError( $req, $err, $code );
}
1;

View File

@ -6,27 +6,18 @@ use Lemonldap::NG::Handler::SharedConf qw(:tsv);
extends 'Lemonldap::NG::Handler::PSGI';
## @method PSGI-Response router($res)
# If PSGI is used as an authentication FastCGI only, this method will be
# called for authenticated users and will set headers in response without
# content.
# @param $req Lemonldap::NG::Common::PSGI::Request
sub router {
return [ 200, [], [] ];
}
sub _run {
my $self = shift;
return sub {
my $req = $_[0];
$self->lmLog( 'New request', 'debug' );
my $res = $self->_authAndTrace(
Lemonldap::NG::Common::PSGI::Request->new( $_[0] ) );
# Transform 302 responses in 401 since Nginx refuse it
if($res->[0] == 302 or $res->[0] == 303) {
$res->[0] = 401;
push @{$res->[1]},'X-Location' => $tsv->{portal}->();
}
# TODO: transform headers in $res->[1]
return $res;
};
my ( $self, $req ) = @_;
my $hdrs = $req->{respHeaders} || {};
return [
200, [ 'Lm-Remote-User', $self->userId, 'Content-Length', 0, %$hdrs ],
[]
];
}
1;