diff --git a/_example/etc/test-nginx.conf b/_example/etc/test-nginx.conf index 1af7f57dc..1b873c48e 100644 --- a/_example/etc/test-nginx.conf +++ b/_example/etc/test-nginx.conf @@ -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; ################################## diff --git a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm index 5ce7c8095..598eb6f74 100644 --- a/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm +++ b/lemonldap-ng-handler/lib/Lemonldap/NG/Handler/Lib/OAuth2.pm @@ -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; diff --git a/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t b/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t new file mode 100644 index 000000000..897a74233 --- /dev/null +++ b/lemonldap-ng-handler/t/71-Lemonldap-NG-Handler-PSGI-OAuth2.t @@ -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(); diff --git a/lemonldap-ng-handler/t/sessions/lock/Apache-Session-f5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock b/lemonldap-ng-handler/t/sessions/lock/.exists similarity index 100% rename from lemonldap-ng-handler/t/sessions/lock/Apache-Session-f5eec18ebb9bc96352595e2d8ce962e8ecf7af7c9a98cb9a43f9cd181cf4b545.lock rename to lemonldap-ng-handler/t/sessions/lock/.exists diff --git a/lemonldap-ng-handler/t/test-psgi-lib.pm b/lemonldap-ng-handler/t/test-psgi-lib.pm index f62d62a01..a44a9381d 100644 --- a/lemonldap-ng-handler/t/test-psgi-lib.pm +++ b/lemonldap-ng-handler/t/test-psgi-lib.pm @@ -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 < '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;