2019-04-22 18:02:14 +02:00
|
|
|
package Lemonldap::NG::Handler::Lib::OAuth2;
|
2021-01-08 17:38:51 +01:00
|
|
|
use Lemonldap::NG::Common::JWT qw(getAccessTokenSessionId);
|
2019-04-22 18:02:14 +02:00
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
2022-02-01 16:33:08 +01:00
|
|
|
our $VERSION = '2.0.14';
|
2019-04-22 18:02:14 +02:00
|
|
|
|
2019-08-22 18:13:09 +02:00
|
|
|
sub retrieveSession {
|
|
|
|
my ( $class, $req, $id ) = @_;
|
|
|
|
my ($offlineId) = $id =~ /^O-(.*)/;
|
|
|
|
|
|
|
|
# Retrieve regular session if this is not an offline access token
|
|
|
|
unless ($offlineId) {
|
2021-03-03 15:36:01 +01:00
|
|
|
my $data =
|
|
|
|
$class->Lemonldap::NG::Handler::Main::retrieveSession( $req, $id );
|
|
|
|
if ( ref($data) eq "HASH" ) {
|
|
|
|
$data = { %{$data}, $class->_getTokenAttributes($req) };
|
|
|
|
|
|
|
|
# Update cache
|
|
|
|
$class->data($data);
|
2021-06-18 19:28:22 +02:00
|
|
|
}
|
|
|
|
else {
|
2021-03-03 15:36:01 +01:00
|
|
|
$req->data->{oauth2_error} = 'invalid_token';
|
|
|
|
}
|
2021-01-19 16:10:26 +01:00
|
|
|
return $data;
|
2019-08-22 18:13:09 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
# 2. Get the session from cache or backend
|
|
|
|
my $session = $req->data->{session} = (
|
|
|
|
Lemonldap::NG::Common::Session->new( {
|
|
|
|
storageModule => $class->tsv->{oidcStorageModule},
|
|
|
|
storageModuleOptions => $class->tsv->{oidcStorageOptions},
|
|
|
|
cacheModule => $class->tsv->{sessionCacheModule},
|
|
|
|
cacheModuleOptions => $class->tsv->{sessionCacheOptions},
|
|
|
|
id => $offlineId,
|
|
|
|
kind => "OIDCI",
|
|
|
|
}
|
|
|
|
)
|
|
|
|
);
|
|
|
|
|
|
|
|
unless ( $session->error ) {
|
|
|
|
|
2021-01-19 16:10:26 +01:00
|
|
|
my $data = { %{ $session->data }, $class->_getTokenAttributes($req) };
|
|
|
|
|
|
|
|
$class->data($data);
|
|
|
|
|
2019-08-22 18:13:09 +02:00
|
|
|
$class->logger->debug("Get session $offlineId from Handler::Main::Run");
|
|
|
|
|
|
|
|
# Verify that session is valid
|
|
|
|
$class->logger->error(
|
|
|
|
"_utime is not defined. This should not happen. Check if it is well transmitted to handler"
|
|
|
|
) unless $session->data->{_utime};
|
|
|
|
|
|
|
|
my $ttl = $class->tsv->{timeout} - time + $session->data->{_utime};
|
|
|
|
$class->logger->debug( "Session TTL = " . $ttl );
|
|
|
|
|
|
|
|
if ( time - $session->data->{_utime} > $class->tsv->{timeout} ) {
|
|
|
|
$class->logger->info("Session $id expired");
|
|
|
|
|
|
|
|
# Clean cached data
|
|
|
|
$class->data( {} );
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-19 16:10:26 +01:00
|
|
|
return $data;
|
2019-08-22 18:13:09 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$class->logger->info("Session $offlineId can't be retrieved");
|
|
|
|
$class->logger->info( $session->error );
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-04-22 18:02:14 +02:00
|
|
|
sub fetchId {
|
|
|
|
my ( $class, $req ) = @_;
|
|
|
|
|
|
|
|
my $access_token;
|
|
|
|
my $authorization = $req->{env}->{HTTP_AUTHORIZATION};
|
|
|
|
|
|
|
|
if ( $authorization
|
|
|
|
and ( ($access_token) = ( $authorization =~ /^Bearer (.+)$/i ) ) )
|
|
|
|
{
|
|
|
|
$class->logger->debug( 'Found OAuth2 access token ' . $access_token );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return $class->Lemonldap::NG::Handler::Main::fetchId($req);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get access token session
|
2021-01-08 17:38:51 +01:00
|
|
|
my $access_token_sid = getAccessTokenSessionId($access_token);
|
|
|
|
unless ($access_token_sid) {
|
|
|
|
$req->data->{oauth2_error} = 'invalid_token';
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
my $infos = $class->getOIDCInfos($access_token_sid);
|
2021-06-18 19:28:22 +02:00
|
|
|
unless ($infos) {
|
|
|
|
$req->data->{oauth2_error} = 'invalid_token';
|
|
|
|
return;
|
|
|
|
}
|
2020-02-20 23:34:02 +01:00
|
|
|
|
2021-01-19 16:10:26 +01:00
|
|
|
# Store scope and rpid for future session attributes
|
|
|
|
if ( $infos->{rp} ) {
|
|
|
|
my $rp = $infos->{rp};
|
2021-09-01 11:40:45 +02:00
|
|
|
$req->data->{_scope} = $infos->{scope};
|
|
|
|
$req->data->{_oidc_grant_type} = $infos->{grant_type};
|
|
|
|
$req->data->{_clientConfKey} = $rp;
|
2021-01-19 16:10:26 +01:00
|
|
|
if ( $class->tsv->{oauth2Options}->{$rp}
|
|
|
|
and $class->tsv->{oauth2Options}->{$rp}->{clientId} )
|
|
|
|
{
|
|
|
|
$req->data->{_clientId} =
|
|
|
|
$class->tsv->{oauth2Options}->{$rp}->{clientId};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-08-22 18:13:09 +02:00
|
|
|
# If this token is tied to a regular session ID
|
2019-05-02 15:58:03 +02:00
|
|
|
if ( my $_session_id = $infos->{user_session_id} ) {
|
2019-04-22 18:02:14 +02:00
|
|
|
$class->logger->debug( 'Get user session id ' . $_session_id );
|
|
|
|
return $_session_id;
|
|
|
|
}
|
2020-02-20 23:34:02 +01:00
|
|
|
|
2019-08-22 18:13:09 +02:00
|
|
|
# If this token is tied to an Offline session
|
|
|
|
if ( my $_session_id = $infos->{offline_session_id} ) {
|
|
|
|
$class->logger->debug( 'Get offline session id ' . $_session_id );
|
|
|
|
return "O-$_session_id";
|
|
|
|
}
|
2019-04-22 18:02:14 +02:00
|
|
|
|
2020-04-22 17:24:09 +02:00
|
|
|
my $value = $class->Lemonldap::NG::Handler::Main::fetchId($req);
|
|
|
|
unless ($value) {
|
|
|
|
$req->data->{oauth2_error} = 'invalid_token';
|
|
|
|
}
|
|
|
|
return $value;
|
2019-04-22 18:02:14 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
## @rmethod protected hash getOIDCInfos(id)
|
|
|
|
# Tries to retrieve the OIDC session, get infos
|
|
|
|
# @return OIDC session infos
|
|
|
|
sub getOIDCInfos {
|
|
|
|
my ( $class, $id ) = @_;
|
|
|
|
my $infos = {};
|
|
|
|
|
|
|
|
# Get the session
|
|
|
|
my $oidcSession = Lemonldap::NG::Common::Session->new( {
|
|
|
|
storageModule => $class->tsv->{oidcStorageModule},
|
|
|
|
storageModuleOptions => $class->tsv->{oidcStorageOptions},
|
|
|
|
cacheModule => $class->tsv->{sessionCacheModule},
|
|
|
|
cacheModuleOptions => $class->tsv->{sessionCacheOptions},
|
|
|
|
id => $id,
|
|
|
|
kind => "OIDCI",
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
unless ( $oidcSession->error ) {
|
|
|
|
$class->logger->debug("Get OIDC session $id");
|
|
|
|
|
2021-06-18 19:28:22 +02:00
|
|
|
# Verify that session is valid
|
|
|
|
unless ( $oidcSession->data->{_utime} ) {
|
|
|
|
$class->logger->error("_utime missing from Access Token session");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
my $ttl = $class->tsv->{timeout} - time + $oidcSession->data->{_utime};
|
|
|
|
$class->logger->debug( "Session TTL = " . $ttl );
|
|
|
|
|
|
|
|
if ( time - $oidcSession->data->{_utime} > $class->tsv->{timeout} ) {
|
|
|
|
$class->logger->info("Access Token session $id expired");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-19 16:10:26 +01:00
|
|
|
$infos = { %{ $oidcSession->data } };
|
2019-04-22 18:02:14 +02:00
|
|
|
}
|
|
|
|
else {
|
|
|
|
$class->logger->info("OIDC Session $id can't be retrieved");
|
|
|
|
$class->logger->info( $oidcSession->error );
|
|
|
|
}
|
|
|
|
|
|
|
|
return $infos;
|
|
|
|
}
|
|
|
|
|
2020-04-22 17:24:09 +02:00
|
|
|
## 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;
|
|
|
|
}
|
|
|
|
|
2021-01-19 16:10:26 +01:00
|
|
|
sub _getTokenAttributes {
|
|
|
|
my ( $class, $req ) = @_;
|
|
|
|
my %res;
|
2021-09-01 11:40:45 +02:00
|
|
|
for my $attr (qw/_scope _clientConfKey _clientId _oidc_grant_type/) {
|
2021-01-19 16:10:26 +01:00
|
|
|
if ( $req->data->{$attr} ) {
|
|
|
|
$res{$attr} = $req->data->{$attr};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return %res;
|
|
|
|
}
|
|
|
|
|
2019-04-22 18:02:14 +02:00
|
|
|
1;
|