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);
* 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
lemonldap-ng (1.4.6-1) unstable; urgency=medium

View File

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

View File

@ -8,7 +8,7 @@ our ( %EXPORT_TAGS, @EXPORT_OK, @EXPORT );
BEGIN {
%EXPORT_TAGS = (
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 => [
qw( &hostname &remote_ip &uri &uri_with_args

View File

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

View File

@ -3,14 +3,15 @@ package Lemonldap::NG::Handler::API::CGI;
our $VERSION = '1.4.0';
# Specific modules and constants for Test or CGI
use constant FORBIDDEN => 403;
use constant REDIRECT => 302;
use constant OK => 0;
use constant DECLINED => 0;
use constant DONE => 0;
use constant SERVER_ERROR => 500;
use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
use constant FORBIDDEN => 403;
use constant HTTP_UNAUTHORIZED => 401;
use constant REDIRECT => 302;
use constant OK => 0;
use constant DECLINED => 0;
use constant DONE => 0;
use constant SERVER_ERROR => 500;
use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
# 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

View File

@ -2,14 +2,15 @@ package Lemonldap::NG::Handler::API::Nginx;
our $VERSION = '1.9.0';
use constant FORBIDDEN => 403;
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 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

View File

@ -4,16 +4,23 @@ use strict;
our $VERSION = '1.9.0';
# Specific modules and constants for Test or CGI
use constant FORBIDDEN => 403;
use constant REDIRECT => 302;
use constant OK => 0;
use constant DECLINED => 0;
use constant DONE => 0;
use constant SERVER_ERROR => 500;
use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
use constant FORBIDDEN => 403;
use constant HTTP_UNAUTHORIZED => 401;
use constant REDIRECT => 302;
use constant OK => 0;
use constant DECLINED => 0;
use constant DONE => 0;
use constant SERVER_ERROR => 500;
use constant AUTH_REQUIRED => 401;
use constant MAINTENANCE => 503;
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)
# 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;
our $VERSION = '1.9.0';
use base Lemonldap::NG::Handler::API::PSGI;
use base 'Lemonldap::NG::Handler::API::PSGI';
sub uri {
return $Lemonldap::NG::Handler::API::PSGI::$request->original_uri;
return $Lemonldap::NG::Handler::API::PSGI::request->original_uri;
}
1;

View File

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

View File

@ -74,29 +74,29 @@ sub _authAndTrace {
# 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"})]
];
}
}
#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 {

View File

@ -15,7 +15,8 @@ sub _run {
return sub {
my $req = $_[0];
$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]
return $res;

View File

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

View File

@ -35,6 +35,9 @@ sub init {
foreach ( keys %$localconf );
}
# Manager needs to keep new Ajax behaviour
$args->{noAjaxHook} = 0;
return 0 unless ( $self->Lemonldap::NG::Handler::PSGI::Router::init($args) );
# 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 {
my $test =
grep( { $_ eq $_[0]; }
map( { $$_{'k'}; } @{ $_[2]{'select'}; } ) );
map( { $_->{'k'}; } @{ $_[2]{'select'}; } ) );
return $test
? 1
: ( 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',
'test' => sub {
my $l = shift();
my (@s) = split( /[\s,]+/, $l, 0 );
my @s = split( /[\s,]+/, $l, 0 );
foreach my $s (@s) {
return 0, qq[Bad ldap uri "$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' => '; ',
'type' => 'authParamsText'
},
'noAjaxHook' => {
'default' => 0,
'type' => 'bool'
},
'notification' => {
'default' => 0,
'type' => 'bool'

View File

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

View File

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

View File

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

View File

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

File diff suppressed because one or more lines are too long