Merge branch 'fix-oauth2-handler-2167' into 'v2.0'

Change OAuth2 handler behavior to conform to RFC

See merge request lemonldap-ng/lemonldap-ng!139
This commit is contained in:
Maxime Besson 2020-05-04 15:56:48 +02:00
commit 00a0aac46a
5 changed files with 225 additions and 43 deletions

View File

@ -64,7 +64,7 @@ server {
# If CDA is used, uncomment this
#auth_request_set $cookie_value $upstream_http_set_cookie;
#add_header Set-Cookie $cookie_value;
# Remove this for AuthBasic handler
# Remove this for AuthBasic and OAuth2 handlers
error_page 401 $lmlocation;
##################################

View File

@ -2,7 +2,7 @@ package Lemonldap::NG::Handler::Lib::OAuth2;
use strict;
our $VERSION = '2.0.4';
our $VERSION = '2.0.8';
sub retrieveSession {
my ( $class, $req, $id ) = @_;
@ -88,7 +88,11 @@ sub fetchId {
return "O-$_session_id";
}
return $class->Lemonldap::NG::Handler::Main::fetchId($req);
my $value = $class->Lemonldap::NG::Handler::Main::fetchId($req);
unless ($value) {
$req->data->{oauth2_error} = 'invalid_token';
}
return $value;
}
## @rmethod protected hash getOIDCInfos(id)
@ -123,4 +127,18 @@ sub getOIDCInfos {
return $infos;
}
## The OAuth2 handler does not redirect, we simply return a 401 with relevant
# information as described in https://tools.ietf.org/html/rfc6750#section-3
sub goToPortal {
my ( $class, $req, $url, $arg, $path ) = @_;
my $oauth2_error = '';
if ( $req->data->{oauth2_error} ) {
$oauth2_error = ' error="' . $req->data->{oauth2_error} . '"';
}
$class->set_header_out( $req,
'WWW-Authenticate' => "Bearer" . $oauth2_error );
return $class->HTTP_UNAUTHORIZED;
}
1;

View File

@ -0,0 +1,158 @@
use Test::More;
BEGIN {
require 't/test-psgi-lib.pm';
}
my $maintests = 10;
init(
'Lemonldap::NG::Handler::Server',
{
logLevel => 'error',
vhostOptions => {
'test1.example.com' => {
vhostHttps => 0,
vhostPort => 80,
vhostMaintenance => 0,
vhostServiceTokenTTL => -1,
},
},
exportedHeaders => {
'test1.example.com' => {
'Auth-User' => '$uid',
},
}
}
);
# Inject an on-line access token session
Lemonldap::NG::Common::Session->new( {
storageModule => 'Apache::Session::File',
storageModuleOptions => { Directory => 't/sessions' },
id =>
'f0fd4e85000ce35d062f97f5b466fc00abc2fad0406e03e086605f929ec4a249',
force => 1,
kind => 'OIDCI',
info => {
"user_session_id" => $sessionId,
"_type" => "access_token",
"_utime" => time,
"rp" => "rp-example2",
"scope" => "openid email"
}
}
);
# Inject an offline access token session
Lemonldap::NG::Common::Session->new( {
storageModule => 'Apache::Session::File',
storageModuleOptions => { Directory => 't/sessions' },
id => '999888777',
force => 1,
kind => 'OIDCI',
info => {
"offline_session_id" => '000999000',
"_type" => "refresh_token",
"_utime" => time,
"rp" => "rp-example",
"scope" => "openid email"
}
}
);
# Inject the refresh token containing user attributes
Lemonldap::NG::Common::Session->new( {
storageModule => 'Apache::Session::File',
storageModuleOptions => { Directory => 't/sessions' },
id => '000999000',
force => 1,
kind => 'OIDCI',
info => {
"_type" => "refresh_token",
"_utime" => time,
"rp" => "rp-example2",
"scope" => "openid email",
'groups' => 'users; timelords',
'uid' => 'dwho',
'cn' => 'Doctor Who',
'hGroups' => {
'users' => {},
'timelords' => {}
},
'ipAddr' => '127.0.0.1',
'mail' => 'dwho@badwolf.org',
'authenticationLevel' => 1,
}
}
);
# Request without Access Token
ok(
$res = $client->_get(
'/test', undef, 'test1.example.com', '', VHOSTTYPE => 'OAuth2',
),
'Unauthenticated request to OAuth2 URL'
);
# Check headers
%h = @{ $res->[1] };
is( $h{'WWW-Authenticate'}, 'Bearer', 'Got WWW-Authenticate: Bearer' );
# Request with invalid Access Token
ok(
$res = $client->_get(
'/test', undef,
'test1.example.com', '',
VHOSTTYPE => 'OAuth2',
HTTP_AUTHORIZATION => 'Bearer 123',
),
'Invalid access token'
);
# Check headers
%h = @{ $res->[1] };
like(
$h{'WWW-Authenticate'},
qr#Bearer.*error="invalid_token"#,
'Got invalid token error'
);
# Request with valid Access Token
ok(
$res = $client->_get(
'/test', undef,
'test1.example.com', '',
VHOSTTYPE => 'OAuth2',
HTTP_AUTHORIZATION =>
'Bearer f0fd4e85000ce35d062f97f5b466fc00abc2fad0406e03e086605f929ec4a249',
),
'Invalid access token'
);
# Check headers
%h = @{ $res->[1] };
is( $res->[0], 200, "Request accepted" );
ok( $h{'Auth-User'} eq 'dwho', 'Header Auth-User is set to "dwho"' )
or explain( \%h, 'Auth-User => "dwho"' );
# Request with Access token from offline session
ok(
$res = $client->_get(
'/test', undef,
'test1.example.com', '',
VHOSTTYPE => 'OAuth2',
HTTP_AUTHORIZATION => 'Bearer 999888777',
),
'Invalid access token'
);
# Check headers
%h = @{ $res->[1] };
is( $res->[0], 200, "Request accepted" );
ok( $h{'Auth-User'} eq 'dwho', 'Header Auth-User is set to "dwho"' )
or explain( \%h, 'Auth-User => "dwho"' );
count($maintests);
done_testing( count() );
clean();

View File

@ -41,48 +41,53 @@ sub init {
);
ok( $client->app, 'App object' ) or explain( $client, '->app...' );
count(3);
open F, ">$file"
or die $!;
my $now = time;
my $ts = strftime "%Y%m%d%H%M%S", localtime;
print F <<EOF;
{
"_startTime" : "$ts",
"_session_kind" : "SSO",
"UA" : "Mozilla/5.0 (X11; VAX4000; rv:43.0) Gecko/20100101 Firefox/143.0 Iceweasel/143.0.1",
"cn" : "Doctor Who",
"_utime" : $now,
"_whatToTrace" : "dwho",
"mail" : "dwho\@badwolf.org",
"_passwordDB" : "Demo",
"_lastAuthnUTime" : $now,
"uid" : "dwho",
"_issuerDB" : "Null",
"_userDB" : "Demo",
"_user" : "dwho",
"_session_id" : "f5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545",
"authenticationLevel" : 1,
"_auth" : "Demo",
"_updateTime" : "$ts",
"_loginHistory" : {
"successLogin" : [
{
"ipAddr" : "127.0.0.1",
"_utime" : $now
}
]
},
"ipAddr" : "127.0.0.1",
"_timezone" : "1",
"groups" : "users; timelords",
"hGroups" : {
"users" : {},
"timelords" : {}
}
}
EOF
close F;
my $sessionData = {
'_timezone' => '1',
'groups' => 'users; timelords',
'uid' => 'dwho',
'_loginHistory' => {
'successLogin' => [ {
'_utime' => $now,
'ipAddr' => '127.0.0.1'
}
]
},
'cn' => 'Doctor Who',
'_lastAuthnUTime' => $now,
'_whatToTrace' => 'dwho',
'_issuerDB' => 'Null',
'_startTime' => "$ts",
'_user' => 'dwho',
'_updateTime' => "$ts",
'_userDB' => 'Demo',
'hGroups' => {
'users' => {},
'timelords' => {}
},
'ipAddr' => '127.0.0.1',
'mail' => 'dwho@badwolf.org',
'authenticationLevel' => 1,
'_utime' => $now,
'_passwordDB' => 'Demo',
'_auth' => 'Demo',
'UA' =>
'Mozilla/5.0 (X11; VAX4000; rv:43.0) Gecko/20100101 Firefox/143.0 Iceweasel/143.0.1'
};
my $as = Lemonldap::NG::Common::Session->new( {
storageModule => 'Apache::Session::File',
storageModuleOptions => { Directory => 't/sessions' },
id => $sessionId,
force => 1,
kind => 'SSO',
info => $sessionData,
}
);
}
sub client {
@ -109,7 +114,8 @@ sub explain {
}
sub clean {
unlink $file;
unlink glob 't/sessions/*';
unlink glob 't/sessions/lock/*';
}
package Lemonldap::NG::Handler::PSGI::Cli::Lib;