Manage Ajax requests redirection with 401 (new parameter noAjaxHook)
This commit is contained in:
parent
0e51658c6f
commit
4f3a42ba48
7
debian/NEWS
vendored
7
debian/NEWS
vendored
|
@ -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
|
||||||
|
|
|
@ -127,6 +127,7 @@ sub defaultValues {
|
||||||
'managerDn' => '',
|
'managerDn' => '',
|
||||||
'managerPassword' => '',
|
'managerPassword' => '',
|
||||||
'multiValuesSeparator' => '; ',
|
'multiValuesSeparator' => '; ',
|
||||||
|
'noAjaxHook' => 0,
|
||||||
'notification' => 0,
|
'notification' => 0,
|
||||||
'notificationStorage' => 'File',
|
'notificationStorage' => 'File',
|
||||||
'notificationStorageOptions' => {
|
'notificationStorageOptions' => {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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'
|
||||||
|
|
|
@ -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/',
|
||||||
|
|
|
@ -607,7 +607,8 @@ sub tree {
|
||||||
'port',
|
'port',
|
||||||
'useRedirectOnForbidden',
|
'useRedirectOnForbidden',
|
||||||
'useRedirectOnError',
|
'useRedirectOnError',
|
||||||
'maintenance'
|
'maintenance',
|
||||||
|
'noAjaxHook'
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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
Loading…
Reference in New Issue
Block a user