OIDC: make getUser optionally use Refresh Tokens (#2713)

This commit is contained in:
Maxime Besson 2022-02-27 12:10:05 +01:00
parent b3b29508d3
commit f8d1d0fc5f
2 changed files with 135 additions and 2 deletions

View File

@ -18,6 +18,7 @@ use Lemonldap::NG::Common::JWT
qw(getAccessTokenSessionId getJWTPayload getJWTHeader getJWTSignature getJWTSignedData);
use MIME::Base64
qw/encode_base64 decode_base64 encode_base64url decode_base64url/;
use Scalar::Util qw/looks_like_number/;
use Mouse;
use Lemonldap::NG::Portal::Main::Constants qw(PE_OK PE_REDIRECT);
@ -703,6 +704,134 @@ sub checkIDTokenValidity {
return 1;
}
# Returns the current OP and a valid Access token
sub getUserInfoParams {
my ( $self, $req ) = @_;
my $op = $req->data->{_oidcOPCurrent};
if ($op) {
# We are in the middle of an auth process,
# access token has just been fetched already
my $access_token = $req->data->{access_token};
return ( $op, $access_token );
}
else {
# Get OP and access token from existing session (refresh)
return $self->getUserInfoParamsFromSession($req);
}
}
sub getUserInfoParamsFromSession {
my ( $self, $req ) = @_;
my $op = $req->userData->{_oidc_OP};
# Save current OP, we will need it for setSessionInfo & friends
$req->data->{_oidcOPCurrent} = $op;
if ($op) {
my $access_token = $req->userData->{_oidc_access_token};
my $access_token_eol = $req->userData->{_oidc_access_token_eol};
if ($access_token_eol) {
return $self->refreshAccessTokenIfExpired( $req, $op );
}
else {
# We don't know the TTL for this access token,
# so we can only hope that it works
return ( $op, $access_token );
}
}
else {
$self->logger->warn("No OP found in session");
return ( $op, undef );
}
}
sub refreshAccessTokenIfExpired {
my ( $self, $req, $op, $session ) = @_;
# Handle unauthenticated OIDC calls
my $data = $session ? $session->data : $req->userData;
my $access_token = $data->{_oidc_access_token};
my $access_token_eol = $data->{_oidc_access_token_eol};
if ( time < $access_token_eol ) {
# Access Token is still valid, return it
return ( $op, $access_token );
}
else {
# Refresh Access Token
return ( $op, $self->refreshAccessToken( $req, $op, $session ) );
}
}
sub refreshAccessToken {
my ( $self, $req, $op, $session ) = @_;
# Handle unauthenticated OIDC calls
my $data = $session ? $session->data : $req->userData;
my $session_id = $session ? $session->id : $req->id;
my $refresh_token = $data->{_oidc_refresh_token};
if ($refresh_token) {
my $content =
$self->getAccessTokenFromTokenEndpoint( $req, $op, 'refresh_token',
{ refresh_token => $refresh_token } );
if ($content) {
my $token_response = $self->decodeTokenResponse($content);
if ($token_response) {
my $access_token = $token_response->{access_token};
my $expires_in = $token_response->{expires_in};
my $refresh_token = $token_response->{refresh_token};
undef $expires_in unless looks_like_number($expires_in);
$self->logger->debug("Access token: $access_token");
$self->logger->debug( "Access token expires in: "
. ( $expires_in || "<unknown>" ) );
$self->logger->debug(
"Refresh token: " . ( $refresh_token || "<none>" ) );
my $updateSession;
# Remember tokens
$updateSession->{_oidc_access_token} = $access_token;
$updateSession->{_oidc_refresh_token} = $refresh_token
if $refresh_token;
# If access token TTL is given save expiration date
# (with security margin)
if ($expires_in) {
$updateSession->{_oidc_access_token_eol} =
time + ( $expires_in * 0.9 );
}
$self->p->updateSession( $req, $updateSession, $session_id );
return ($access_token);
}
else {
$self->logger->warn("Could not decode Token Response for $op");
return undef;
}
}
else {
$self->logger->warn("Could not fetch new Access Token for $op");
return undef;
}
}
else {
$self->logger->warn("No Refresh Token was found for $op");
return undef;
}
}
# Get UserInfo response
# return String UserInfo response decoded content
sub getUserInfo {

View File

@ -27,7 +27,8 @@ sub init {
sub getUser {
my ( $self, $req ) = @_;
my $op = $req->data->{_oidcOPCurrent};
my ( $op, $access_token ) = $self->getUserInfoParams($req);
# This is likely to happen when running getUser without extractFormInfo
# see #1980
@ -36,7 +37,10 @@ sub getUser {
return PE_ERROR;
}
my $access_token = $req->data->{access_token};
unless ($access_token) {
$self->logger->warn("Could not get Access Token for User Info request");
return PE_ERROR;
}
my $userinfo_content = $self->getUserInfo( $op, $access_token );