Manage Ajax requests redirection with 401 (new parameter noAjaxHook)

This commit is contained in:
Xavier Guimard 2016-01-28 06:43:46 +00:00
parent 0e51658c6f
commit 4f3a42ba48
19 changed files with 127 additions and 76 deletions

7
debian/NEWS vendored
View File

@ -9,6 +9,13 @@ lemonldap-ng (1.9.0-1) UNRELEASED; urgency=low
user and if users use the menu); user and if users use the menu);
* manager server * manager server
To request for authentication, handlers sent a 302 HTTP code even if request
was an Ajax one. For now, a 401 code will be send with a WWW-Authenticate
header containing portal URL. This is a little HTTP protocol hook created
because browsers follow redirection tranparently.
If you want to keep old behaviour, set noAjaxHook to 1 (in General Parameters
-> Advanced -> Handler redirections -> Keep redirections for Ajax).
-- Xavier Guimard <x.guimard@free.fr> Thu, 21 Jan 2016 17:13:07 +0100 -- Xavier Guimard <x.guimard@free.fr> Thu, 21 Jan 2016 17:13:07 +0100
lemonldap-ng (1.4.6-1) unstable; urgency=medium lemonldap-ng (1.4.6-1) unstable; urgency=medium

View File

@ -127,6 +127,7 @@ sub defaultValues {
'managerDn' => '', 'managerDn' => '',
'managerPassword' => '', 'managerPassword' => '',
'multiValuesSeparator' => '; ', 'multiValuesSeparator' => '; ',
'noAjaxHook' => 0,
'notification' => 0, 'notification' => 0,
'notificationStorage' => 'File', 'notificationStorage' => 'File',
'notificationStorageOptions' => { 'notificationStorageOptions' => {

View File

@ -8,7 +8,7 @@ our ( %EXPORT_TAGS, @EXPORT_OK, @EXPORT );
BEGIN { BEGIN {
%EXPORT_TAGS = ( %EXPORT_TAGS = (
httpCodes => [ httpCodes => [
qw( MP OK REDIRECT FORBIDDEN DONE DECLINED SERVER_ERROR AUTH_REQUIRED MAINTENANCE ) qw( MP OK REDIRECT HTTP_UNAUTHORIZED FORBIDDEN DONE DECLINED SERVER_ERROR AUTH_REQUIRED MAINTENANCE )
], ],
functions => [ functions => [
qw( &hostname &remote_ip &uri &uri_with_args qw( &hostname &remote_ip &uri &uri_with_args

View File

@ -13,14 +13,15 @@ use Apache2::Const;
use Apache2::Filter; use Apache2::Filter;
use APR::Table; use APR::Table;
use constant FORBIDDEN => Apache2::Const::FORBIDDEN; use constant FORBIDDEN => Apache2::Const::FORBIDDEN;
use constant REDIRECT => Apache2::Const::REDIRECT; use constant HTTP_UNAUTHORIZED => Apache2::Const::HTTP_UNAUTHORIZED;
use constant OK => Apache2::Const::OK; use constant REDIRECT => Apache2::Const::REDIRECT;
use constant DECLINED => Apache2::Const::DECLINED; use constant OK => Apache2::Const::OK;
use constant DONE => Apache2::Const::DONE; use constant DECLINED => Apache2::Const::DECLINED;
use constant SERVER_ERROR => Apache2::Const::SERVER_ERROR; use constant DONE => Apache2::Const::DONE;
use constant AUTH_REQUIRED => Apache2::Const::AUTH_REQUIRED; use constant SERVER_ERROR => Apache2::Const::SERVER_ERROR;
use constant MAINTENANCE => Apache2::Const::HTTP_SERVICE_UNAVAILABLE; use constant AUTH_REQUIRED => Apache2::Const::AUTH_REQUIRED;
use constant MAINTENANCE => Apache2::Const::HTTP_SERVICE_UNAVAILABLE;
eval { require threads::shared; }; eval { require threads::shared; };
print STDERR print STDERR

View File

@ -3,14 +3,15 @@ package Lemonldap::NG::Handler::API::CGI;
our $VERSION = '1.4.0'; our $VERSION = '1.4.0';
# Specific modules and constants for Test or CGI # Specific modules and constants for Test or CGI
use constant FORBIDDEN => 403; use constant FORBIDDEN => 403;
use constant REDIRECT => 302; use constant HTTP_UNAUTHORIZED => 401;
use constant OK => 0; use constant REDIRECT => 302;
use constant DECLINED => 0; use constant OK => 0;
use constant DONE => 0; use constant DECLINED => 0;
use constant SERVER_ERROR => 500; use constant DONE => 0;
use constant AUTH_REQUIRED => 401; use constant SERVER_ERROR => 500;
use constant MAINTENANCE => 503; use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
# Log level, since it can't be set in server config # Log level, since it can't be set in server config
# Default value 'notice' can be changed in lemonldap-ng.ini or in init args # Default value 'notice' can be changed in lemonldap-ng.ini or in init args

View File

@ -2,14 +2,15 @@ package Lemonldap::NG::Handler::API::Nginx;
our $VERSION = '1.9.0'; our $VERSION = '1.9.0';
use constant FORBIDDEN => 403; use constant FORBIDDEN => 403;
use constant REDIRECT => 302; use constant HTTP_UNAUTHORIZED => 401;
use constant OK => 0; use constant REDIRECT => 302;
use constant DECLINED => -1; use constant OK => 0;
use constant DONE => -2; use constant DECLINED => -1;
use constant SERVER_ERROR => 500; use constant DONE => -2;
use constant AUTH_REQUIRED => 401; use constant SERVER_ERROR => 500;
use constant MAINTENANCE => 503; use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
my $request; # Nginx object for current request my $request; # Nginx object for current request

View File

@ -4,16 +4,23 @@ use strict;
our $VERSION = '1.9.0'; our $VERSION = '1.9.0';
# Specific modules and constants for Test or CGI # Specific modules and constants for Test or CGI
use constant FORBIDDEN => 403; use constant FORBIDDEN => 403;
use constant REDIRECT => 302; use constant HTTP_UNAUTHORIZED => 401;
use constant OK => 0; use constant REDIRECT => 302;
use constant DECLINED => 0; use constant OK => 0;
use constant DONE => 0; use constant DECLINED => 0;
use constant SERVER_ERROR => 500; use constant DONE => 0;
use constant AUTH_REQUIRED => 401; use constant SERVER_ERROR => 500;
use constant MAINTENANCE => 503; use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
our $request; our $request;
#
## @method void setServerSignature(string sign)
# modifies web server signature
# @param $sign String to add to server signature
sub setServerSignature {
}
## @method void thread_share(string $variable) ## @method void thread_share(string $variable)
# share or not the variable (if authorized by specific module) # share or not the variable (if authorized by specific module)

View File

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

View File

@ -69,7 +69,7 @@ sub updateStatus {
# Used to reject non authorized requests. # Used to reject non authorized requests.
# Inform the status processus and call logForbidden(). # Inform the status processus and call logForbidden().
# @param $uri URI # @param $uri URI
# @return Apache2::Const::REDIRECT or Apache2::Const::FORBIDDEN # @return Apache2::Const::FORBIDDEN
sub forbidden { sub forbidden {
my $class = shift; my $class = shift;
my $uri = Lemonldap::NG::Handler::API->unparsed_uri; my $uri = Lemonldap::NG::Handler::API->unparsed_uri;
@ -134,18 +134,35 @@ sub encodeUrl {
# @return Apache2::Const::REDIRECT # @return Apache2::Const::REDIRECT
sub goToPortal { sub goToPortal {
my ( $class, $url, $arg ) = @_; my ( $class, $url, $arg ) = @_;
Lemonldap::NG::Handler::Main::Logger->lmLog( my ( $ret, $msg );
"Redirect "
. Lemonldap::NG::Handler::API->remote_ip
. " to portal (url was $url)",
'debug'
);
my $urlc_init = $class->encodeUrl($url); my $urlc_init = $class->encodeUrl($url);
Lemonldap::NG::Handler::API->set_header_out( if ( !$tsv->{noAjaxHook}
'Location' => &{ $tsv->{portal} }() and Lemonldap::NG::Handler::API->header_in('Accept') =~
. "?url=$urlc_init" m|application/json| )
. ( $arg ? "&$arg" : "" ) ); {
return REDIRECT; $msg =
"Ajax request, send 401 instead of 302 "
. Lemonldap::NG::Handler::API->remote_ip
. " to portal (url was $url)";
Lemonldap::NG::Handler::API->set_header_out(
'WWW-Authenticate' => &{ $tsv->{portal} }()
. "?url=$urlc_init"
. ( $arg ? "&$arg" : "" ) );
$ret = HTTP_UNAUTHORIZED;
}
else {
$msg =
"Redirect "
. Lemonldap::NG::Handler::API->remote_ip
. " to portal (url was $url)";
Lemonldap::NG::Handler::API->set_header_out(
'Location' => &{ $tsv->{portal} }()
. "?url=$urlc_init"
. ( $arg ? "&$arg" : "" ) );
$ret = REDIRECT;
}
Lemonldap::NG::Handler::Main::Logger->lmLog( $msg, 'debug' );
return $ret;
} }
## @rmethod protected $ fetchId() ## @rmethod protected $ fetchId()

View File

@ -74,29 +74,29 @@ sub _authAndTrace {
# is not really HTTP compliant but nothing in this # is not really HTTP compliant but nothing in this
# protocol can do this. Our javascripts understand that # protocol can do this. Our javascripts understand that
# they have to prompt user with the URL # they have to prompt user with the URL
elsif ( #elsif (
$req->accept =~ m|application/json| # $req->accept =~ m|application/json|
or ( $req->contentType # or ( $req->contentType
and $req->contentType =~ m|application/json| ) # and $req->contentType =~ m|application/json| )
) # )
{ #{
$self->lmLog( 'Ajax request detected', 'debug' ); # $self->lmLog( 'Ajax request detected', 'debug' );
if ( $res == 302 or $res == 303 ) { # if ( $res == 302 or $res == 303 ) {
$self->lmLog( 'Rewrite redirection to 401 response', 'debug' ); # $self->lmLog( 'Rewrite redirection to 401 response', 'debug' );
return [ # return [
401, [ 'WWW-Authenticate' => $req->{respHeaders}->{Location} ], [''] # 401, [ 'WWW-Authenticate' => $req->{respHeaders}->{Location} ], ['']
]; # ];
} # }
else { # else {
$self->lmLog( # $self->lmLog(
"Lemonldap::NG::Handler::SharedConf::run() returns $res", # "Lemonldap::NG::Handler::SharedConf::run() returns $res",
'debug' ); # 'debug' );
return [ # return [
$res, [ 'Content-Type', 'application/json' ], # $res, [ 'Content-Type', 'application/json' ],
[qq({"error":"$res"})] # [qq({"error":"$res"})]
]; # ];
} # }
} #}
# Non Ajax requests may be redirected to portal # Non Ajax requests may be redirected to portal
else { else {

View File

@ -15,7 +15,8 @@ sub _run {
return sub { return sub {
my $req = $_[0]; my $req = $_[0];
$self->lmLog( 'New request', 'debug' ); $self->lmLog( 'New request', 'debug' );
my $res = $self->_authAndTrace($req); my $res = $self->_authAndTrace(
Lemonldap::NG::Common::PSGI::Request->new( $_[0] ) );
# TODO: transform headers in $res->[1] # TODO: transform headers in $res->[1]
return $res; return $res;

View File

@ -111,7 +111,7 @@ sub defaultValuesInit {
cda cookieExpiration cookieName cda cookieExpiration cookieName
customFunctions httpOnly securedCookie customFunctions httpOnly securedCookie
timeoutActivity useRedirectOnError useRedirectOnForbidden timeoutActivity useRedirectOnError useRedirectOnForbidden
useSafeJail whatToTrace useSafeJail noAjaxHook whatToTrace
) )
); );

View File

@ -35,6 +35,9 @@ sub init {
foreach ( keys %$localconf ); foreach ( keys %$localconf );
} }
# Manager needs to keep new Ajax behaviour
$args->{noAjaxHook} = 0;
return 0 unless ( $self->Lemonldap::NG::Handler::PSGI::Router::init($args) ); return 0 unless ( $self->Lemonldap::NG::Handler::PSGI::Router::init($args) );
# TODO: manage errors # TODO: manage errors

View File

@ -186,7 +186,7 @@ qr/^(?:(?:\-+\s*BEGIN\s+(?:PUBLIC\s+KEY|CERTIFICATE)\s*\-+\r?\n)?[a-zA-Z0-9\/\+\
'test' => sub { 'test' => sub {
my $test = my $test =
grep( { $_ eq $_[0]; } grep( { $_ eq $_[0]; }
map( { $$_{'k'}; } @{ $_[2]{'select'}; } ) ); map( { $_->{'k'}; } @{ $_[2]{'select'}; } ) );
return $test return $test
? 1 ? 1
: ( 0, "Invalid value '$_[0]' for this select" ); : ( 0, "Invalid value '$_[0]' for this select" );
@ -1011,7 +1011,7 @@ qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-
'default' => 'ldap://localhost', 'default' => 'ldap://localhost',
'test' => sub { 'test' => sub {
my $l = shift(); my $l = shift();
my (@s) = split( /[\s,]+/, $l, 0 ); my @s = split( /[\s,]+/, $l, 0 );
foreach my $s (@s) { foreach my $s (@s) {
return 0, qq[Bad ldap uri "$s"] return 0, qq[Bad ldap uri "$s"]
unless $s =~ unless $s =~
@ -1146,6 +1146,10 @@ qr/^(?:(?:(?:(?:[a-zA-Z0-9][-a-zA-Z0-9]*)?[a-zA-Z0-9])[.])*(?:[a-zA-Z][-a-zA-Z0-
'default' => '; ', 'default' => '; ',
'type' => 'authParamsText' 'type' => 'authParamsText'
}, },
'noAjaxHook' => {
'default' => 0,
'type' => 'bool'
},
'notification' => { 'notification' => {
'default' => 0, 'default' => 0,
'type' => 'bool' 'type' => 'bool'

View File

@ -239,6 +239,11 @@ sub attributes {
type => 'bool', type => 'bool',
documentation => 'Maintenance mode for all virtual hosts', documentation => 'Maintenance mode for all virtual hosts',
}, },
noAjaxHook => {
default => 0,
type => 'bool',
documentation => 'Avoid replacing 302 by 401 for Ajax responses',
},
portal => { portal => {
type => 'url', type => 'url',
default => 'http://auth.example.com/', default => 'http://auth.example.com/',

View File

@ -607,7 +607,8 @@ sub tree {
'port', 'port',
'useRedirectOnForbidden', 'useRedirectOnForbidden',
'useRedirectOnError', 'useRedirectOnError',
'maintenance' 'maintenance',
'noAjaxHook'
] ]
}, },
{ {

View File

@ -325,6 +325,7 @@
"newRSAKey": "New keys", "newRSAKey": "New keys",
"newRule": "New rule", "newRule": "New rule",
"next": "Next", "next": "Next",
"noAjaxHook": "Keep redirections for Ajax",
"noDatas": "No datas to display", "noDatas": "No datas to display",
"notABoolean": "Not a boolean", "notABoolean": "Not a boolean",
"notAnInteger": "Not an integer", "notAnInteger": "Not an integer",

View File

@ -325,6 +325,7 @@
"newRSAKey": "Nouvelles clefs", "newRSAKey": "Nouvelles clefs",
"newRule": "Nouvelle règle", "newRule": "Nouvelle règle",
"next": "Suivante", "next": "Suivante",
"noAjaxHook": "Conserver les redirections pour Ajax",
"noDatas": "Aucune donnée à afficher", "noDatas": "Aucune donnée à afficher",
"notABoolean": "Pas un booléen", "notABoolean": "Pas un booléen",
"notAnInteger": "Pas un nombre entier", "notAnInteger": "Pas un nombre entier",

File diff suppressed because one or more lines are too long