2015-01-20 11:04:44 +01:00
|
|
|
## @file
|
|
|
|
# OpenIDConnect Issuer file
|
|
|
|
|
|
|
|
## @class
|
|
|
|
# OpenIDConnect Issuer class
|
|
|
|
package Lemonldap::NG::Portal::IssuerDBOpenIDConnect;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
use Lemonldap::NG::Portal::Simple;
|
2015-03-30 17:57:23 +02:00
|
|
|
use String::Random qw(random_string);
|
2015-01-20 11:04:44 +01:00
|
|
|
use base qw(Lemonldap::NG::Portal::_OpenIDConnect);
|
|
|
|
|
2016-03-17 23:19:44 +01:00
|
|
|
our $VERSION = '2.0.0';
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
## @method void issuerDBInit()
|
2015-01-23 14:06:54 +01:00
|
|
|
# Get configuration data
|
2015-01-20 11:04:44 +01:00
|
|
|
# @return Lemonldap::NG::Portal error code
|
|
|
|
sub issuerDBInit {
|
2015-01-23 14:06:54 +01:00
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
return PE_ERROR unless $self->loadRPs;
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @apmethod int issuerForUnAuthUser()
|
|
|
|
# Get OIDC request
|
|
|
|
# @return Lemonldap::NG::Portal error code
|
|
|
|
sub issuerForUnAuthUser {
|
|
|
|
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
my $issuerDBOpenIDConnectPath = $self->{issuerDBOpenIDConnectPath};
|
2015-01-28 14:07:11 +01:00
|
|
|
my $authorize_uri = $self->{oidcServiceMetaDataAuthorizeURI};
|
|
|
|
my $token_uri = $self->{oidcServiceMetaDataTokenURI};
|
2015-02-04 14:25:13 +01:00
|
|
|
my $userinfo_uri = $self->{oidcServiceMetaDataUserInfoURI};
|
2015-03-27 15:21:43 +01:00
|
|
|
my $jwks_uri = $self->{oidcServiceMetaDataJWKSURI};
|
2015-03-30 17:57:23 +02:00
|
|
|
my $registration_uri = $self->{oidcServiceMetaDataRegistrationURI};
|
2015-04-02 09:02:21 +02:00
|
|
|
my $endsession_uri = $self->{oidcServiceMetaDataEndSessionURI};
|
2015-06-01 20:22:36 +02:00
|
|
|
my $checksession_uri = $self->{oidcServiceMetaDataCheckSessionURI};
|
2015-01-28 14:07:11 +01:00
|
|
|
my $issuer = $self->{oidcServiceMetaDataIssuer};
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
# Called URL
|
|
|
|
my $url = $self->url();
|
|
|
|
my $url_path = $self->url( -absolute => 1 );
|
|
|
|
$url_path =~ s#^//#/#;
|
|
|
|
|
|
|
|
# AUTHORIZE
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${authorize_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect AUTHORIZE URL",
|
|
|
|
'debug' );
|
|
|
|
|
2015-03-25 12:53:03 +01:00
|
|
|
# Get and save parameters
|
|
|
|
my $oidc_request = {};
|
2015-03-24 18:01:15 +01:00
|
|
|
foreach my $param (
|
2015-04-30 08:09:51 +02:00
|
|
|
qw/response_type scope client_id state redirect_uri nonce response_mode display prompt max_age ui_locales id_token_hint login_hint acr_values request request_uri/
|
2015-03-24 18:01:15 +01:00
|
|
|
)
|
2015-01-20 11:04:44 +01:00
|
|
|
{
|
2015-03-25 12:53:03 +01:00
|
|
|
$oidc_request->{$param} = $self->getHiddenFormValue($param)
|
|
|
|
|| $self->param($param);
|
|
|
|
$self->lmLog(
|
|
|
|
"OIDC request parameter $param: " . $oidc_request->{$param},
|
|
|
|
'debug' );
|
|
|
|
$self->setHiddenFormValue( $param, $oidc_request->{$param} );
|
2015-01-20 11:04:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-25 11:54:00 +01:00
|
|
|
# Detect requested flow
|
2015-03-25 12:53:03 +01:00
|
|
|
my $response_type = $oidc_request->{'response_type'};
|
2015-03-25 11:54:00 +01:00
|
|
|
my $flow = $self->getFlowType($response_type);
|
|
|
|
|
|
|
|
unless ($flow) {
|
|
|
|
$self->lmLog( "Unknown response type: $response_type", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
$self->lmLog(
|
|
|
|
"OIDC $flow flow requested (response type: $response_type)",
|
|
|
|
'debug' );
|
|
|
|
|
2015-04-30 08:09:51 +02:00
|
|
|
# Extract request_uri/request parameter
|
|
|
|
if ( $oidc_request->{'request_uri'} ) {
|
|
|
|
my $request =
|
|
|
|
$self->getRequestJWT( $oidc_request->{'request_uri'} );
|
|
|
|
|
|
|
|
if ($request) {
|
|
|
|
$oidc_request->{'request'} = $request;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog( "Error with Request URI resolution", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:16:32 +02:00
|
|
|
if ( $oidc_request->{'request'} ) {
|
2015-04-25 17:19:12 +02:00
|
|
|
|
2015-04-22 19:16:32 +02:00
|
|
|
my $request = $self->getJWTJSONData( $oidc_request->{'request'} );
|
|
|
|
|
|
|
|
# Override OIDC parameters by request content
|
|
|
|
foreach ( keys %$request ) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Override $_ OIDC param by value present in request parameter",
|
|
|
|
'debug'
|
|
|
|
);
|
|
|
|
$oidc_request->{$_} = $request->{$_};
|
|
|
|
$self->setHiddenFormValue( $_, $request->{$_} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-25 12:53:03 +01:00
|
|
|
# State
|
|
|
|
my $state = $oidc_request->{'state'};
|
|
|
|
|
2015-03-25 11:54:00 +01:00
|
|
|
# Check redirect_uri
|
2015-03-25 12:53:03 +01:00
|
|
|
my $redirect_uri = $oidc_request->{'redirect_uri'};
|
|
|
|
unless ($redirect_uri) {
|
2015-03-25 11:54:00 +01:00
|
|
|
$self->lmLog( "Redirect URI is required", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check display
|
2015-03-25 12:53:03 +01:00
|
|
|
my $display = $oidc_request->{'display'};
|
2015-03-25 11:54:00 +01:00
|
|
|
if ( $display eq "page" ) {
|
|
|
|
$self->lmLog( "Display type page will be used", 'debug' );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog(
|
|
|
|
"Display type $display not supported, display type page will be used",
|
|
|
|
'debug'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check prompt
|
2015-03-25 12:53:03 +01:00
|
|
|
my $prompt = $oidc_request->{'prompt'};
|
2015-03-25 14:48:36 +01:00
|
|
|
if ( $prompt =~ /\bnone\b/ ) {
|
2015-03-25 11:54:00 +01:00
|
|
|
$self->lmLog(
|
|
|
|
"Prompt type none requested, but user needs to authenticate",
|
|
|
|
'error' );
|
2015-03-25 12:53:03 +01:00
|
|
|
$self->returnRedirectError( $redirect_uri, "login_required",
|
2015-03-25 11:54:00 +01:00
|
|
|
"Prompt type none requested",
|
2015-03-25 12:53:03 +01:00
|
|
|
undef, $state, ( $flow ne "authorizationcode" ) );
|
2015-03-25 11:54:00 +01:00
|
|
|
}
|
|
|
|
|
2015-03-25 12:15:31 +01:00
|
|
|
# Check ui_locales
|
2015-03-25 12:53:03 +01:00
|
|
|
my $ui_locales = $oidc_request->{'ui_locales'};
|
2015-03-25 12:15:31 +01:00
|
|
|
if ( defined $ui_locales ) {
|
|
|
|
my $lang = join( ',', split( /\s+/, $ui_locales ) );
|
|
|
|
$self->{lang} = $self->extract_lang($lang);
|
|
|
|
}
|
|
|
|
|
2015-03-25 12:40:58 +01:00
|
|
|
# Check login_hint
|
2015-03-25 12:53:03 +01:00
|
|
|
my $login_hint = $oidc_request->{'login_hint'};
|
2015-03-25 12:40:58 +01:00
|
|
|
if ( defined $login_hint ) {
|
|
|
|
$self->{user} ||= $login_hint;
|
|
|
|
}
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# TOKEN
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${token_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect TOKEN URL",
|
|
|
|
'debug' );
|
|
|
|
|
2015-02-02 10:44:33 +01:00
|
|
|
# Check authentication
|
|
|
|
my ( $client_id, $client_secret ) =
|
|
|
|
$self->getEndPointAuthenticationCredentials();
|
|
|
|
|
|
|
|
unless ( $client_id && $client_secret ) {
|
|
|
|
$self->lmLog(
|
|
|
|
"No authentication provided to get token, or authentication type not supported",
|
|
|
|
"error"
|
|
|
|
);
|
|
|
|
$self->returnJSONError("unauthorized_client");
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Verify that client_id is registered in configuration
|
|
|
|
my $rp = $self->getRP($client_id);
|
|
|
|
|
|
|
|
unless ($rp) {
|
|
|
|
$self->lmLog(
|
2015-03-11 17:24:10 +01:00
|
|
|
"No registered Relying Party found with client_id $client_id",
|
|
|
|
'error' );
|
2015-02-02 10:44:33 +01:00
|
|
|
$self->returnJSONError("unauthorized_client");
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog( "Client id $client_id match RP $rp", 'debug' );
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check client_secret
|
|
|
|
unless ( $client_secret eq $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsClientSecret} )
|
|
|
|
{
|
|
|
|
$self->lmLog( "Wrong credentials", "error" );
|
|
|
|
$self->returnJSONError("access_denied");
|
|
|
|
$self->quit;
|
|
|
|
}
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
# Get code session
|
|
|
|
my $code = $self->param('code');
|
|
|
|
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog( "OpenID Connect Code: $code", 'debug' );
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
my $codeSession = $self->getOpenIDConnectSession($code);
|
|
|
|
|
|
|
|
unless ($codeSession) {
|
|
|
|
$self->lmLog( "Unable to find OIDC session $code", "error" );
|
|
|
|
$self->returnJSONError("invalid_request");
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Check we have the same redirect_uri value
|
|
|
|
unless (
|
|
|
|
$self->param("redirect_uri") eq $codeSession->data->{redirect_uri} )
|
|
|
|
{
|
|
|
|
$self->lmLog(
|
|
|
|
"Provided redirect_uri is different from "
|
|
|
|
. $codeSession->{redirect_uri},
|
|
|
|
"error"
|
|
|
|
);
|
|
|
|
$self->returnJSONError("invalid_request");
|
2015-01-28 17:53:06 +01:00
|
|
|
$codeSession->remove();
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get user identifier
|
|
|
|
my $apacheSession =
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->getApacheSession( $codeSession->data->{user_session_id}, 1 );
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
unless ($apacheSession) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Unable to find user session linked to OIDC session $code",
|
|
|
|
"error" );
|
|
|
|
$self->returnJSONError("invalid_request");
|
2015-01-28 17:53:06 +01:00
|
|
|
$codeSession->remove();
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-03-11 17:16:37 +01:00
|
|
|
my $user_id_attribute = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsUserIDAttr} || $self->{whatToTrace};
|
|
|
|
my $user_id = $apacheSession->data->{$user_id_attribute};
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog( "Found corresponding user: $user_id", 'debug' );
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
# Generate access_token
|
|
|
|
my $accessTokenSession = $self->getOpenIDConnectSession;
|
|
|
|
|
|
|
|
unless ($accessTokenSession) {
|
|
|
|
$self->lmLog( "Unable to create OIDC session for access_token",
|
|
|
|
"error" );
|
|
|
|
$self->returnJSONError("invalid_request");
|
2015-01-28 17:53:06 +01:00
|
|
|
$codeSession->remove();
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-02-04 14:25:13 +01:00
|
|
|
# Store data in access token
|
|
|
|
$accessTokenSession->update(
|
|
|
|
{
|
|
|
|
scope => $codeSession->data->{scope},
|
|
|
|
rp => $rp,
|
2015-02-19 16:17:49 +01:00
|
|
|
user_session_id => $apacheSession->id,
|
|
|
|
_utime => time,
|
2015-02-04 14:25:13 +01:00
|
|
|
}
|
|
|
|
);
|
2015-01-31 16:17:56 +01:00
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
my $access_token = $accessTokenSession->id;
|
|
|
|
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog( "Generated access token: $access_token", 'debug' );
|
|
|
|
|
2015-03-23 18:04:00 +01:00
|
|
|
# Compute hash to store in at_hash
|
|
|
|
my $alg = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenSignAlg};
|
|
|
|
my ($hash_level) = ( $alg =~ /(?:\w{2})(\d{3})/ );
|
|
|
|
my $at_hash = $self->createHash( $access_token, $hash_level );
|
|
|
|
|
2015-02-19 19:04:29 +01:00
|
|
|
# ID token payload
|
2015-03-13 11:54:36 +01:00
|
|
|
my $id_token_exp = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenExpiration};
|
2015-06-18 12:20:15 +02:00
|
|
|
$id_token_exp += time;
|
2015-03-13 11:54:36 +01:00
|
|
|
|
2015-03-13 13:54:04 +01:00
|
|
|
my $id_token_acr = "loa-" . $apacheSession->data->{authenticationLevel};
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
my $id_token_payload_hash = {
|
2015-03-13 11:54:36 +01:00
|
|
|
iss => $issuer, # Issuer Identifier
|
|
|
|
sub => $user_id, # Subject Identifier
|
2015-03-13 13:54:04 +01:00
|
|
|
aud => [$client_id], # Audience
|
2015-03-13 11:54:36 +01:00
|
|
|
exp => $id_token_exp, # expiration
|
|
|
|
iat => time, # Issued time
|
|
|
|
auth_time =>
|
|
|
|
$apacheSession->data->{_lastAuthnUTime}, # Authentication time
|
2015-03-13 13:54:04 +01:00
|
|
|
acr => $id_token_acr, # Authentication Context Class Reference
|
|
|
|
azp => $client_id, # Authorized party
|
|
|
|
# TODO amr
|
2015-01-20 11:04:44 +01:00
|
|
|
};
|
|
|
|
|
2015-03-17 13:56:11 +01:00
|
|
|
my $nonce = $codeSession->data->{nonce};
|
|
|
|
$id_token_payload_hash->{nonce} = $nonce if defined $nonce;
|
2015-03-23 18:04:00 +01:00
|
|
|
$id_token_payload_hash->{'at_hash'} = $at_hash if $at_hash;
|
2015-03-17 13:56:11 +01:00
|
|
|
|
2015-02-19 19:04:29 +01:00
|
|
|
# Create ID Token
|
|
|
|
my $id_token = $self->createIDToken( $id_token_payload_hash, $rp );
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-02-19 19:04:29 +01:00
|
|
|
$self->lmLog( "Generated id token: $id_token", 'debug' );
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
# Send token response
|
2015-03-13 12:09:39 +01:00
|
|
|
my $expires_in = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsAccessTokenExpiration};
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
my $token_response = {
|
|
|
|
access_token => $access_token,
|
|
|
|
token_type => 'Bearer',
|
|
|
|
expires_in => $expires_in,
|
2015-01-28 14:41:10 +01:00
|
|
|
id_token => $id_token,
|
2015-01-20 11:04:44 +01:00
|
|
|
};
|
|
|
|
|
|
|
|
$self->returnJSON($token_response);
|
|
|
|
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog( "Token response sent", 'debug' );
|
|
|
|
|
2015-01-28 17:53:06 +01:00
|
|
|
$codeSession->remove();
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->quit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-02-04 14:25:13 +01:00
|
|
|
# USERINFO
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${userinfo_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect USERINFO URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
my $access_token = $self->getEndPointAccessToken();
|
|
|
|
|
|
|
|
unless ($access_token) {
|
|
|
|
$self->lmLog( "Unable to get access_token", "error" );
|
|
|
|
$self->returnBearerError( "invalid_request",
|
|
|
|
"Access token not found in request" );
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->lmLog( "Received Access Token $access_token", 'debug' );
|
|
|
|
|
|
|
|
my $accessTokenSession = $self->getOpenIDConnectSession($access_token);
|
|
|
|
|
|
|
|
unless ($accessTokenSession) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Unable to get access token session for id $access_token",
|
|
|
|
"error" );
|
|
|
|
$self->returnBearerError( "invalid_token",
|
|
|
|
"Access Token not found or expired" );
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
|
|
|
# Get access token session data
|
|
|
|
my $scope = $accessTokenSession->data->{scope};
|
|
|
|
my $rp = $accessTokenSession->data->{rp};
|
|
|
|
my $user_session_id = $accessTokenSession->data->{user_session_id};
|
|
|
|
|
|
|
|
my $userinfo_response =
|
|
|
|
$self->buildUserInfoResponse( $scope, $rp, $user_session_id );
|
|
|
|
|
2015-04-14 20:42:02 +02:00
|
|
|
my $userinfo_sign_alg = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsUserInfoSignAlg};
|
|
|
|
|
|
|
|
unless ($userinfo_sign_alg) {
|
|
|
|
$self->returnJSON($userinfo_response);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
my $userinfo_jwt =
|
|
|
|
$self->createJWT( $userinfo_response, $userinfo_sign_alg, $rp );
|
|
|
|
print $self->header('application/jwt');
|
|
|
|
print $userinfo_jwt;
|
|
|
|
$self->lmLog( "Return UserInfo as JWT: $userinfo_jwt", 'debug' );
|
|
|
|
}
|
2015-02-04 14:25:13 +01:00
|
|
|
|
|
|
|
$self->lmLog( "UserInfo response sent", 'debug' );
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-27 15:21:43 +01:00
|
|
|
# JWKS
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${jwks_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect JWKS URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
my $jwks = { keys => [] };
|
|
|
|
|
|
|
|
my $public_key_sig = $self->{oidcServicePublicKeySig};
|
2015-06-05 14:55:06 +02:00
|
|
|
my $key_id_sig = $self->{oidcServiceKeyIdSig};
|
2015-03-27 15:21:43 +01:00
|
|
|
if ($public_key_sig) {
|
|
|
|
my $key = $self->key2jwks($public_key_sig);
|
|
|
|
$key->{kty} = "RSA";
|
|
|
|
$key->{use} = "sig";
|
2015-06-05 14:55:06 +02:00
|
|
|
$key->{kid} = $key_id_sig if $key_id_sig;
|
2015-03-27 15:21:43 +01:00
|
|
|
push @{ $jwks->{keys} }, $key;
|
|
|
|
}
|
|
|
|
|
|
|
|
$self->returnJSON($jwks);
|
|
|
|
|
|
|
|
$self->lmLog( "JWKS response sent", 'debug' );
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2015-03-30 17:57:23 +02:00
|
|
|
# REGISTRATION
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${registration_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect REGISTRATION URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# TODO: check Initial Access Token
|
|
|
|
|
|
|
|
# Specific message to allow DOS detection
|
|
|
|
my $source_ip = $self->ipAddr;
|
|
|
|
$self->lmLog( "OpenID Connect Registration request from $source_ip",
|
|
|
|
'warn' );
|
|
|
|
|
2015-05-29 12:07:54 +02:00
|
|
|
# Check dynamic registration is allowed
|
|
|
|
unless ( $self->{oidcServiceAllowDynamicRegistration} ) {
|
|
|
|
$self->lmLog( "Dynamic registration is not allowed", 'error' );
|
|
|
|
$self->returnJSONError( 'server_error',
|
|
|
|
'Dynamic registration is not allowed' );
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-03-30 17:57:23 +02:00
|
|
|
# Get client metadata
|
|
|
|
my $client_metadata_json = $self->param('POSTDATA');
|
|
|
|
|
|
|
|
$self->lmLog( "Client metadata received: $client_metadata_json",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
my $client_metadata = $self->decodeJSON($client_metadata_json);
|
|
|
|
my $registration_response = {};
|
|
|
|
|
|
|
|
# Check redirect_uris
|
|
|
|
unless ( $client_metadata->{redirect_uris} ) {
|
|
|
|
$self->lmLog( "Field redirect_uris is mandatory", 'error' );
|
|
|
|
$self->returnJSONError( 'invalid_client_metadata',
|
|
|
|
'Field redirect_uris is mandatory' );
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
|
|
|
# RP identifier
|
|
|
|
my $registration_time = time;
|
|
|
|
my $rp = "register-$registration_time";
|
|
|
|
|
|
|
|
# Generate Client ID and Client Password
|
2015-05-29 10:50:51 +02:00
|
|
|
my $client_id = random_string("ssssssssssssssssssssssssssssss");
|
|
|
|
my $client_secret = random_string("ssssssssssssssssssssssssssssss");
|
2015-03-30 17:57:23 +02:00
|
|
|
|
|
|
|
# Register known parameters
|
|
|
|
my $client_name =
|
|
|
|
$client_metadata->{client_name} || "Self registered client";
|
|
|
|
my $logo_uri = $client_metadata->{logo_uri};
|
|
|
|
my $id_token_signed_response_alg =
|
|
|
|
$client_metadata->{id_token_signed_response_alg} || "RS256";
|
2015-05-29 10:50:51 +02:00
|
|
|
my $userinfo_signed_response_alg =
|
|
|
|
$client_metadata->{userinfo_signed_response_alg};
|
|
|
|
my $redirect_uris = $client_metadata->{redirect_uris};
|
|
|
|
|
|
|
|
# Register RP in global configuration
|
|
|
|
my $conf = $self->__lmConf->getConf();
|
|
|
|
|
|
|
|
$conf->{cfgAuthor} = "OpenID Connect Registration ($client_name)";
|
|
|
|
$conf->{cfgAuthorIP} = $source_ip;
|
|
|
|
|
|
|
|
$conf->{oidcRPMetaDataExportedVars}->{$rp} = {};
|
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}->{oidcRPMetaDataOptionsClientID}
|
|
|
|
= $client_id;
|
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsClientSecret} = $client_secret;
|
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsDisplayName} = $client_name;
|
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}->{oidcRPMetaDataOptionsIcon} =
|
|
|
|
$logo_uri;
|
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenSignAlg} =
|
2015-03-30 17:57:23 +02:00
|
|
|
$id_token_signed_response_alg;
|
2015-05-29 10:50:51 +02:00
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsRedirectUris} = join( ' ', @$redirect_uris );
|
|
|
|
$conf->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsUserInfoSignAlg} =
|
|
|
|
$userinfo_signed_response_alg
|
|
|
|
if defined $userinfo_signed_response_alg;
|
|
|
|
|
|
|
|
if ( $self->__lmConf->saveConf($conf) ) {
|
|
|
|
|
|
|
|
# Reload RP list
|
|
|
|
$self->loadRPs(1);
|
|
|
|
|
|
|
|
# Send registration response
|
|
|
|
$registration_response->{'client_id'} = $client_id;
|
|
|
|
$registration_response->{'client_secret'} = $client_secret;
|
|
|
|
$registration_response->{'client_id_issued_at'} =
|
|
|
|
$registration_time;
|
|
|
|
$registration_response->{'client_id_expires_at'} = 0;
|
|
|
|
$registration_response->{'client_name'} = $client_name;
|
|
|
|
$registration_response->{'logo_uri'} = $logo_uri;
|
|
|
|
$registration_response->{'id_token_signed_response_alg'} =
|
|
|
|
$id_token_signed_response_alg;
|
|
|
|
$registration_response->{'redirect_uris'} = $redirect_uris;
|
|
|
|
$registration_response->{'userinfo_signed_response_alg'} =
|
|
|
|
$userinfo_signed_response_alg
|
|
|
|
if defined $userinfo_signed_response_alg;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog(
|
|
|
|
"Configuration not saved: $Lemonldap::NG::Common::Conf::msg",
|
|
|
|
'error' );
|
|
|
|
$self->returnJSONError( 'server_error', 'Configuration not saved' );
|
|
|
|
$self->quit;
|
|
|
|
}
|
2015-03-30 17:57:23 +02:00
|
|
|
|
|
|
|
# TODO: return 201 HTTP code
|
|
|
|
$self->returnJSON($registration_response);
|
|
|
|
|
|
|
|
$self->lmLog( "Registration response sent", 'debug' );
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-04-02 09:02:21 +02:00
|
|
|
# END SESSION
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${endsession_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect END SESSION URL",
|
|
|
|
'debug' );
|
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
$self->lmLog( "User is already logged out", 'debug' );
|
2015-04-02 09:02:21 +02:00
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
my $post_logout_redirect_uri = $self->param('post_logout_redirect_uri');
|
|
|
|
my $state = $self->param('state');
|
2015-04-02 09:02:21 +02:00
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
if ($post_logout_redirect_uri) {
|
2015-04-02 09:02:21 +02:00
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
# Build Response
|
|
|
|
my $response_url =
|
|
|
|
$self->buildLogoutResponse( $post_logout_redirect_uri, $state );
|
2015-04-02 09:02:21 +02:00
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
$self->lmLog( "Redirect user to $response_url", 'debug' );
|
|
|
|
$self->{'urldc'} = $response_url;
|
2015-04-02 09:02:21 +02:00
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
$self->_sub('autoRedirect');
|
2015-04-02 09:02:21 +02:00
|
|
|
}
|
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
return PE_LOGOUT_OK;
|
2015-04-02 09:02:21 +02:00
|
|
|
}
|
|
|
|
|
2015-06-01 20:22:36 +02:00
|
|
|
# CHECK SESSION
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${checksession_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog(
|
|
|
|
"URL $url detected as an OpenID Connect CHECK SESSION URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
print $self->header(
|
|
|
|
-type => 'text/html',
|
|
|
|
-access_control_allow_origin => '*'
|
|
|
|
);
|
|
|
|
print $self->start_html(
|
|
|
|
-title => 'Check Session',
|
|
|
|
-script => [
|
|
|
|
{
|
|
|
|
-type => 'text/javascript',
|
|
|
|
-src =>
|
|
|
|
'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
-type => 'text/javascript',
|
|
|
|
-src =>
|
|
|
|
'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js'
|
|
|
|
},
|
|
|
|
{ -code => $self->getSessionManagementOPIFrameJS }
|
|
|
|
]
|
|
|
|
);
|
|
|
|
print $self->end_html();
|
|
|
|
$self->quit();
|
|
|
|
}
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @apmethod int issuerForAuthUser()
|
|
|
|
# Do nothing
|
|
|
|
# @return Lemonldap::NG::Portal error code
|
|
|
|
sub issuerForAuthUser {
|
|
|
|
|
|
|
|
my $self = shift;
|
|
|
|
|
|
|
|
my $issuerDBOpenIDConnectPath = $self->{issuerDBOpenIDConnectPath};
|
2015-04-02 18:54:00 +02:00
|
|
|
my $authorize_uri = $self->{oidcServiceMetaDataAuthorizeURI};
|
|
|
|
my $token_uri = $self->{oidcServiceMetaDataTokenURI};
|
|
|
|
my $userinfo_uri = $self->{oidcServiceMetaDataUserInfoURI};
|
2015-03-27 15:21:43 +01:00
|
|
|
my $jwks_uri = $self->{oidcServiceMetaDataJWKSURI};
|
2015-03-30 17:57:23 +02:00
|
|
|
my $registration_uri = $self->{oidcServiceMetaDataRegistrationURI};
|
2015-04-02 18:54:00 +02:00
|
|
|
my $endsession_uri = $self->{oidcServiceMetaDataEndSessionURI};
|
2015-06-01 20:22:36 +02:00
|
|
|
my $checksession_uri = $self->{oidcServiceMetaDataCheckSessionURI};
|
2015-03-17 12:01:11 +01:00
|
|
|
my $issuer = $self->{oidcServiceMetaDataIssuer};
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
# Session ID
|
|
|
|
my $session_id = $self->{sessionInfo}->{_session_id} || $self->{id};
|
|
|
|
|
|
|
|
# Called URL
|
|
|
|
my $url = $self->url();
|
|
|
|
my $url_path = $self->url( -absolute => 1 );
|
|
|
|
$url_path =~ s#^//#/#;
|
|
|
|
|
|
|
|
# AUTHORIZE
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${authorize_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect AUTHORIZE URL",
|
|
|
|
'debug' );
|
|
|
|
|
2015-01-31 15:34:52 +01:00
|
|
|
# Get and save parameters
|
2015-01-20 11:04:44 +01:00
|
|
|
my $oidc_request = {};
|
2015-03-24 18:01:15 +01:00
|
|
|
foreach my $param (
|
2015-04-30 08:09:51 +02:00
|
|
|
qw/response_type scope client_id state redirect_uri nonce response_mode display prompt max_age ui_locales id_token_hint login_hint acr_valuesi request request_uri/
|
2015-03-24 18:01:15 +01:00
|
|
|
)
|
2015-01-20 11:04:44 +01:00
|
|
|
{
|
|
|
|
$oidc_request->{$param} = $self->getHiddenFormValue($param)
|
|
|
|
|| $self->param($param);
|
|
|
|
$self->lmLog(
|
|
|
|
"OIDC request parameter $param: " . $oidc_request->{$param},
|
|
|
|
'debug' );
|
2015-01-31 15:34:52 +01:00
|
|
|
$self->setHiddenFormValue( $param, $oidc_request->{$param} );
|
2015-01-20 11:04:44 +01:00
|
|
|
}
|
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
# Detect requested flow
|
|
|
|
my $response_type = $oidc_request->{'response_type'};
|
2015-03-25 11:54:00 +01:00
|
|
|
my $flow = $self->getFlowType($response_type);
|
2015-03-16 18:00:56 +01:00
|
|
|
|
|
|
|
unless ($flow) {
|
|
|
|
$self->lmLog( "Unknown response type: $response_type", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
$self->lmLog(
|
|
|
|
"OIDC $flow flow requested (response type: $response_type)",
|
|
|
|
'debug' );
|
|
|
|
|
2015-04-30 08:09:51 +02:00
|
|
|
# Extract request_uri/request parameter
|
|
|
|
if ( $oidc_request->{'request_uri'} ) {
|
|
|
|
my $request =
|
|
|
|
$self->getRequestJWT( $oidc_request->{'request_uri'} );
|
|
|
|
|
|
|
|
if ($request) {
|
|
|
|
$oidc_request->{'request'} = $request;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog( "Error with Request URI resolution", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-22 19:16:32 +02:00
|
|
|
if ( $oidc_request->{'request'} ) {
|
|
|
|
my $request = $self->getJWTJSONData( $oidc_request->{'request'} );
|
|
|
|
|
|
|
|
# Override OIDC parameters by request content
|
|
|
|
foreach ( keys %$request ) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Override $_ OIDC param by value present in request parameter",
|
|
|
|
'debug'
|
|
|
|
);
|
|
|
|
$oidc_request->{$_} = $request->{$_};
|
|
|
|
$self->setHiddenFormValue( $_, $request->{$_} );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-24 18:01:15 +01:00
|
|
|
# Check all required parameters
|
|
|
|
unless ( $oidc_request->{'redirect_uri'} ) {
|
|
|
|
$self->lmLog( "Redirect URI is required", 'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
unless ( $oidc_request->{'scope'} ) {
|
|
|
|
$self->lmLog( "Scope is required", 'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"invalid_request",
|
|
|
|
"scope required",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
unless ( $oidc_request->{'client_id'} ) {
|
|
|
|
$self->lmLog( "Client ID is required", 'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"invalid_request",
|
|
|
|
"client_id required",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
|
|
|
}
|
2015-03-16 18:00:56 +01:00
|
|
|
if ( $flow eq "implicit" and not defined $oidc_request->{'nonce'} ) {
|
|
|
|
$self->lmLog( "Nonce is required for implicit flow", 'error' );
|
2015-03-24 17:40:00 +01:00
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"invalid_request", "nonce required",
|
|
|
|
undef, $oidc_request->{'state'}, 1
|
|
|
|
);
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-05-29 18:49:32 +02:00
|
|
|
# Check if flow is allowed
|
|
|
|
if ( $flow eq "authorizationcode"
|
|
|
|
and not $self->{oidcServiceAllowAuthorizationCodeFlow} )
|
|
|
|
{
|
|
|
|
$self->lmLog( "Authorization code flow is not allowed", 'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"server_error", "Authorization code flow not allowed",
|
|
|
|
undef, $oidc_request->{'state'}, 0
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if ( $flow eq "implicit" and not $self->{oidcServiceAllowImplicitFlow} )
|
|
|
|
{
|
|
|
|
$self->lmLog( "Implicit flow is not allowed", 'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"server_error", "Implicit flow not allowed",
|
|
|
|
undef, $oidc_request->{'state'}, 1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if ( $flow eq "hybrid" and not $self->{oidcServiceAllowHybridFlow} ) {
|
|
|
|
$self->lmLog( "Hybrid flow is not allowed", 'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"server_error", "Hybrid flow not allowed",
|
|
|
|
undef, $oidc_request->{'state'}, 1
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-03-25 17:11:45 +01:00
|
|
|
# Check if user needs to be reauthenticated
|
|
|
|
my $reauthentication = 0;
|
|
|
|
my $prompt = $oidc_request->{'prompt'};
|
2015-03-25 15:55:46 +01:00
|
|
|
if ( $prompt =~ /\blogin\b/ ) {
|
2015-03-25 17:11:45 +01:00
|
|
|
$self->lmLog(
|
|
|
|
"Reauthentication requested by Relying Party in prompt parameter",
|
|
|
|
'debug'
|
|
|
|
);
|
|
|
|
$reauthentication = 1;
|
|
|
|
}
|
2015-03-25 15:55:46 +01:00
|
|
|
|
2015-03-25 17:11:45 +01:00
|
|
|
my $max_age = $oidc_request->{'max_age'};
|
|
|
|
my $_lastAuthnUTime = $self->{sessionInfo}->{_lastAuthnUTime};
|
2015-03-25 17:13:09 +01:00
|
|
|
if ( $max_age && time > $_lastAuthnUTime + $max_age ) {
|
2015-03-25 17:11:45 +01:00
|
|
|
$self->lmLog(
|
|
|
|
"Reauthentication forced cause authentication time ($_lastAuthnUTime) is too old (>$max_age s)",
|
|
|
|
'debug'
|
|
|
|
);
|
|
|
|
$reauthentication = 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($reauthentication) {
|
2015-03-25 15:55:46 +01:00
|
|
|
|
|
|
|
# Replay authentication process
|
|
|
|
$self->{updateSession} = 1;
|
|
|
|
$self->{error} = $self->_subProcess(
|
|
|
|
qw(issuerDBInit authInit issuerForUnAuthUser extractFormInfo
|
|
|
|
userDBInit getUser setAuthSessionInfo setSessionInfo
|
|
|
|
setMacros setGroups setPersistentSessionInfo
|
|
|
|
setLocalGroups authenticate store authFinish)
|
|
|
|
);
|
|
|
|
|
|
|
|
# Return error if any
|
|
|
|
return $self->{error} if ( $self->{error} > 0 );
|
|
|
|
|
|
|
|
# Disable further reauthentication
|
|
|
|
$prompt =~ s/\blogin\b//;
|
|
|
|
$self->setHiddenFormValue( 'prompt', $prompt );
|
2015-04-07 11:04:17 +02:00
|
|
|
|
|
|
|
# Update session_id
|
|
|
|
$session_id = $self->{sessionInfo}->{_session_id} || $self->{id};
|
2015-03-25 15:55:46 +01:00
|
|
|
}
|
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
# Check openid scope
|
|
|
|
unless ( $oidc_request->{'scope'} =~ /\bopenid\b/ ) {
|
|
|
|
$self->lmLog( "No openid scope found", 'debug' );
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
#TODO manage standard OAuth request
|
|
|
|
return PE_OK;
|
|
|
|
}
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
# Check client_id
|
2015-03-17 12:01:11 +01:00
|
|
|
my $client_id = $oidc_request->{'client_id'};
|
|
|
|
$self->lmLog( "Request from client id $client_id", 'debug' );
|
2015-03-16 18:00:56 +01:00
|
|
|
|
|
|
|
# Verify that client_id is registered in configuration
|
2015-03-17 12:01:11 +01:00
|
|
|
my $rp = $self->getRP($client_id);
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
unless ($rp) {
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->lmLog(
|
2015-03-17 12:01:11 +01:00
|
|
|
"No registered Relying Party found with client_id $client_id",
|
|
|
|
'error' );
|
2015-03-24 17:40:00 +01:00
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"invalid_request",
|
|
|
|
"client_id $client_id unknown",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-03-17 12:01:11 +01:00
|
|
|
$self->lmLog( "Client id $client_id match RP $rp", 'debug' );
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-04-25 17:19:12 +02:00
|
|
|
# Check Request JWT signature
|
|
|
|
if ( $oidc_request->{'request'} ) {
|
|
|
|
unless (
|
|
|
|
$self->verifyJWTSignature(
|
|
|
|
$oidc_request->{'request'},
|
|
|
|
undef, $rp
|
|
|
|
)
|
|
|
|
)
|
|
|
|
{
|
|
|
|
$self->lmLog( "Request JWT signature could not be verified",
|
|
|
|
'error' );
|
|
|
|
return PE_ERROR;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog( "Request JWT signature verified", 'debug' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-30 14:58:56 +02:00
|
|
|
# Check redirect_uri
|
|
|
|
my $redirect_uri = $oidc_request->{'redirect_uri'};
|
|
|
|
my $redirect_uris = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsRedirectUris};
|
|
|
|
|
|
|
|
if ($redirect_uris) {
|
|
|
|
my $redirect_uri_allowed = 0;
|
|
|
|
foreach ( split( /\s+/, $redirect_uris ) ) {
|
|
|
|
$redirect_uri_allowed = 1 if $redirect_uri eq $_;
|
|
|
|
}
|
|
|
|
unless ($redirect_uri_allowed) {
|
|
|
|
$self->lmLog( "Redirect URI $redirect_uri not allowed",
|
|
|
|
'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"invalid_request",
|
|
|
|
"redirect_uri $redirect_uri not allowed",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-25 17:13:09 +01:00
|
|
|
# Check id_token_hint
|
|
|
|
my $id_token_hint = $oidc_request->{'id_token_hint'};
|
|
|
|
if ($id_token_hint) {
|
|
|
|
|
|
|
|
$self->lmLog( "Check sub of ID Token $id_token_hint", 'debug' );
|
|
|
|
|
|
|
|
# Check that id_token_hint sub match current user
|
|
|
|
my $sub = $self->getIDTokenSub($id_token_hint);
|
|
|
|
my $user_id_attribute = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsUserIDAttr} || $self->{whatToTrace};
|
|
|
|
my $user_id = $self->{sessionInfo}->{$user_id_attribute};
|
|
|
|
unless ( $sub eq $user_id ) {
|
|
|
|
$self->lmLog(
|
|
|
|
"ID Token hint sub $sub do not match user $user_id",
|
|
|
|
'error' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"invalid_request",
|
|
|
|
"current user do not match id_token_hint sub",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog( "ID Token hint sub $sub match current user",
|
|
|
|
'debug' );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
# Obtain consent
|
|
|
|
my $ask_for_consent = 1;
|
|
|
|
if ( $self->{sessionInfo}->{"_oidc_consent_time_$rp"}
|
|
|
|
and $self->{sessionInfo}->{"_oidc_consent_scope_$rp"} )
|
|
|
|
{
|
|
|
|
$ask_for_consent = 0;
|
|
|
|
my $consent_time = $self->{sessionInfo}->{"_oidc_consent_time_$rp"};
|
|
|
|
my $consent_scope =
|
|
|
|
$self->{sessionInfo}->{"_oidc_consent_scope_$rp"};
|
2015-01-23 14:06:54 +01:00
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
$self->lmLog(
|
|
|
|
"Consent already given for Relying Party $rp (time: $consent_time, scope: $consent_scope)",
|
|
|
|
'debug'
|
|
|
|
);
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
# Check accepted scope
|
|
|
|
foreach
|
|
|
|
my $requested_scope ( split( /\s+/, $oidc_request->{'scope'} ) )
|
2015-03-11 14:53:58 +01:00
|
|
|
{
|
2015-03-16 18:00:56 +01:00
|
|
|
if ( $consent_scope =~ /\b$requested_scope\b/ ) {
|
|
|
|
$self->lmLog( "Scope $requested_scope already accepted",
|
|
|
|
'debug' );
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$self->lmLog(
|
|
|
|
"Scope $requested_scope was not previously accepted",
|
|
|
|
'debug' );
|
|
|
|
$ask_for_consent = 1;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
2015-03-25 15:55:46 +01:00
|
|
|
|
|
|
|
# Check prompt parameter
|
|
|
|
$ask_for_consent = 1 if ( $prompt =~ /\bconsent\b/ );
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
|
|
|
if ($ask_for_consent) {
|
|
|
|
if ( $self->param('confirm') == 1 ) {
|
|
|
|
$self->updatePersistentSession(
|
|
|
|
{ "_oidc_consent_time_$rp" => time } );
|
|
|
|
$self->updatePersistentSession(
|
|
|
|
{
|
|
|
|
"_oidc_consent_scope_$rp" => $oidc_request->{'scope'}
|
|
|
|
}
|
2015-03-11 14:53:58 +01:00
|
|
|
);
|
2015-03-16 18:00:56 +01:00
|
|
|
$self->lmLog( "Consent given for Relying Party $rp", 'debug' );
|
|
|
|
}
|
2015-03-25 10:11:46 +01:00
|
|
|
elsif ( $self->param('confirm') == -1 ) {
|
|
|
|
$self->lmLog( "User refused consent for Relying party $rp",
|
|
|
|
'debug' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
2015-03-25 15:55:46 +01:00
|
|
|
"consent_required",
|
2015-03-25 10:11:46 +01:00
|
|
|
"consent not given",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
|
|
|
}
|
2015-03-16 18:00:56 +01:00
|
|
|
else {
|
|
|
|
$self->lmLog( "Obtain user consent for Relying Party $rp",
|
|
|
|
'debug' );
|
|
|
|
|
2015-03-27 09:25:36 +01:00
|
|
|
# Return error if prompt is none
|
|
|
|
if ( $prompt =~ /\bnone\b/ ) {
|
|
|
|
$self->lmLog( "Consent is needed but prompt is none",
|
|
|
|
'debug' );
|
|
|
|
$self->returnRedirectError(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
|
|
|
"consent_required",
|
|
|
|
"consent required",
|
|
|
|
undef,
|
|
|
|
$oidc_request->{'state'},
|
|
|
|
( $flow ne "authorizationcode" )
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
my $display_name = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsDisplayName};
|
|
|
|
my $icon = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIcon};
|
2015-06-16 17:34:11 +02:00
|
|
|
my $img_src;
|
2015-03-16 18:00:56 +01:00
|
|
|
my $portalPath = $self->{portal};
|
|
|
|
$portalPath =~ s#^https?://[^/]+/?#/#;
|
|
|
|
$portalPath =~ s#[^/]+\.pl$##;
|
|
|
|
|
2015-06-16 17:34:11 +02:00
|
|
|
if ($icon) {
|
|
|
|
$img_src =
|
|
|
|
( $icon =~ m#^https?://# )
|
|
|
|
? $icon
|
|
|
|
: $portalPath . "skins/common/" . $icon;
|
|
|
|
}
|
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
$self->info('<div class="oidc_consent_message">');
|
2015-06-16 17:34:11 +02:00
|
|
|
$self->info( '<img src="' . $img_src . '" />' ) if $img_src;
|
2015-03-16 18:00:56 +01:00
|
|
|
$self->info( '<h3>'
|
|
|
|
. sprintf( $self->msg(PM_OIDC_CONSENT), $display_name )
|
|
|
|
. '</h3>' );
|
|
|
|
$self->info('<ul>');
|
2015-03-11 14:53:58 +01:00
|
|
|
|
|
|
|
foreach my $requested_scope (
|
2015-03-16 18:00:56 +01:00
|
|
|
split( /\s/, $oidc_request->{'scope'} ) )
|
2015-03-11 14:53:58 +01:00
|
|
|
{
|
2015-03-16 18:00:56 +01:00
|
|
|
my $message;
|
|
|
|
my $scope_messages = {
|
|
|
|
openid => PM_OIDC_SCOPE_OPENID,
|
|
|
|
profile => PM_OIDC_SCOPE_PROFILE,
|
|
|
|
email => PM_OIDC_SCOPE_EMAIL,
|
|
|
|
address => PM_OIDC_SCOPE_ADDRESS,
|
|
|
|
phone => PM_OIDC_SCOPE_PHONE,
|
|
|
|
};
|
|
|
|
if ( $scope_messages->{$requested_scope} ) {
|
|
|
|
$message =
|
|
|
|
$self->msg( $scope_messages->{$requested_scope} );
|
2015-03-11 14:53:58 +01:00
|
|
|
}
|
|
|
|
else {
|
2015-03-16 18:00:56 +01:00
|
|
|
$message = $self->msg(PM_OIDC_SCOPE_OTHER) . " "
|
|
|
|
. $requested_scope;
|
2015-03-11 14:53:58 +01:00
|
|
|
}
|
2015-03-16 18:00:56 +01:00
|
|
|
$self->info("<li>$message</li>");
|
2015-03-11 14:53:58 +01:00
|
|
|
}
|
2015-03-16 18:00:56 +01:00
|
|
|
$self->info('</ul>');
|
|
|
|
$self->info('</div>');
|
|
|
|
$self->{activeTimer} = 0;
|
|
|
|
return PE_CONFIRM;
|
2015-01-31 15:34:52 +01:00
|
|
|
}
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
2015-01-31 15:34:52 +01:00
|
|
|
|
2015-04-07 11:04:17 +02:00
|
|
|
# Create session_state
|
|
|
|
my $session_state =
|
|
|
|
$self->createSessionState( $session_id, $client_id );
|
|
|
|
|
2015-03-16 18:00:56 +01:00
|
|
|
# Authorization Code Flow
|
|
|
|
if ( $flow eq "authorizationcode" ) {
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
# Generate code
|
|
|
|
my $codeSession = $self->getOpenIDConnectSession();
|
|
|
|
my $code = $codeSession->id();
|
|
|
|
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog( "Generated code: $code", 'debug' );
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
# Store data in session
|
|
|
|
$codeSession->update(
|
|
|
|
{
|
|
|
|
redirect_uri => $oidc_request->{'redirect_uri'},
|
2015-01-31 16:17:56 +01:00
|
|
|
scope => $oidc_request->{'scope'},
|
2015-01-20 11:04:44 +01:00
|
|
|
user_session_id => $session_id,
|
2015-02-19 16:17:49 +01:00
|
|
|
_utime => time,
|
2015-03-17 13:56:11 +01:00
|
|
|
nonce => $oidc_request->{'nonce'},
|
2015-01-20 11:04:44 +01:00
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2015-03-11 16:47:24 +01:00
|
|
|
# Build Response
|
2015-04-07 11:04:17 +02:00
|
|
|
my $response_url = $self->buildAuthorizationCodeAuthnResponse(
|
2015-03-11 16:47:24 +01:00
|
|
|
$oidc_request->{'redirect_uri'},
|
2015-04-07 11:04:17 +02:00
|
|
|
$code, $oidc_request->{'state'},
|
|
|
|
$session_state
|
|
|
|
);
|
2015-01-20 11:04:44 +01:00
|
|
|
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog( "Redirect user to $response_url", 'debug' );
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->{'urldc'} = $response_url;
|
|
|
|
|
|
|
|
$self->_sub('autoRedirect');
|
|
|
|
}
|
2015-03-16 18:00:56 +01:00
|
|
|
|
|
|
|
# Implicit Flow
|
|
|
|
if ( $flow eq "implicit" ) {
|
|
|
|
|
2015-03-17 12:01:11 +01:00
|
|
|
my $access_token;
|
2015-03-19 18:04:13 +01:00
|
|
|
my $at_hash;
|
2015-03-16 18:00:56 +01:00
|
|
|
|
2015-03-17 12:01:11 +01:00
|
|
|
if ( $response_type =~ /\btoken\b/ ) {
|
|
|
|
|
|
|
|
# Generate access_token
|
|
|
|
my $accessTokenSession = $self->getOpenIDConnectSession;
|
|
|
|
|
|
|
|
unless ($accessTokenSession) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Unable to create OIDC session for access_token",
|
|
|
|
"error" );
|
2015-03-25 10:11:46 +01:00
|
|
|
$self->returnRedirectError( $oidc_request->{'redirect_uri'},
|
|
|
|
"server_error", undef,
|
|
|
|
undef, $oidc_request->{'state'}, 1 );
|
2015-03-17 12:01:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# Store data in access token
|
|
|
|
$accessTokenSession->update(
|
|
|
|
{
|
|
|
|
scope => $oidc_request->{'scope'},
|
|
|
|
rp => $rp,
|
|
|
|
user_session_id => $session_id,
|
|
|
|
_utime => time,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
$access_token = $accessTokenSession->id;
|
|
|
|
|
|
|
|
$self->lmLog( "Generated access token: $access_token",
|
|
|
|
'debug' );
|
2015-03-19 18:04:13 +01:00
|
|
|
|
|
|
|
# Compute hash to store in at_hash
|
|
|
|
my $alg = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenSignAlg};
|
|
|
|
my ($hash_level) = ( $alg =~ /(?:\w{2})(\d{3})/ );
|
2015-03-23 12:54:22 +01:00
|
|
|
$at_hash = $self->createHash( $access_token, $hash_level );
|
2015-03-17 12:01:11 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# ID token payload
|
|
|
|
my $id_token_exp = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenExpiration};
|
2015-06-18 12:20:15 +02:00
|
|
|
$id_token_exp += time;
|
2015-03-17 12:01:11 +01:00
|
|
|
|
2015-04-01 17:45:08 +02:00
|
|
|
my $authenticationLevel =
|
|
|
|
$self->{sessionInfo}->{authenticationLevel};
|
|
|
|
my $id_token_acr;
|
|
|
|
foreach ( keys %{ $self->{oidcServiceMetaDataAuthnContext} } ) {
|
|
|
|
if ( $self->{oidcServiceMetaDataAuthnContext}->{$_} eq
|
|
|
|
$authenticationLevel )
|
|
|
|
{
|
|
|
|
$id_token_acr = $_;
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
}
|
2015-03-17 12:01:11 +01:00
|
|
|
|
|
|
|
my $user_id_attribute = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsUserIDAttr} || $self->{whatToTrace};
|
|
|
|
my $user_id = $self->{sessionInfo}->{$user_id_attribute};
|
|
|
|
|
|
|
|
my $id_token_payload_hash = {
|
|
|
|
iss => $issuer, # Issuer Identifier
|
|
|
|
sub => $user_id, # Subject Identifier
|
|
|
|
aud => [$client_id], # Audience
|
|
|
|
exp => $id_token_exp, # expiration
|
|
|
|
iat => time, # Issued time
|
2015-04-01 17:45:08 +02:00
|
|
|
auth_time => $self->{sessionInfo}->{_lastAuthnUTime}
|
|
|
|
, # Authentication time
|
|
|
|
azp => $client_id, # Authorized party
|
|
|
|
# TODO amr
|
2015-03-17 12:01:11 +01:00
|
|
|
nonce => $oidc_request->{'nonce'} # Nonce
|
|
|
|
};
|
|
|
|
|
2015-03-19 18:04:13 +01:00
|
|
|
$id_token_payload_hash->{'at_hash'} = $at_hash if $at_hash;
|
2015-04-01 17:45:08 +02:00
|
|
|
$id_token_payload_hash->{'acr'} = $id_token_acr
|
|
|
|
if $id_token_acr;
|
2015-03-19 18:04:13 +01:00
|
|
|
|
2015-03-17 12:01:11 +01:00
|
|
|
# Create ID Token
|
|
|
|
my $id_token = $self->createIDToken( $id_token_payload_hash, $rp );
|
|
|
|
|
|
|
|
$self->lmLog( "Generated id token: $id_token", 'debug' );
|
|
|
|
|
|
|
|
# Send token response
|
|
|
|
my $expires_in = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsAccessTokenExpiration};
|
|
|
|
|
|
|
|
# Build Response
|
2015-04-07 11:04:17 +02:00
|
|
|
my $response_url = $self->buildImplicitAuthnResponse(
|
2015-03-17 12:01:11 +01:00
|
|
|
$oidc_request->{'redirect_uri'},
|
2015-04-07 11:04:17 +02:00
|
|
|
$access_token, $id_token, $expires_in, $oidc_request->{'state'},
|
|
|
|
$session_state
|
|
|
|
);
|
2015-03-17 12:01:11 +01:00
|
|
|
|
|
|
|
$self->lmLog( "Redirect user to $response_url", 'debug' );
|
|
|
|
$self->{'urldc'} = $response_url;
|
|
|
|
|
|
|
|
$self->_sub('autoRedirect');
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# Hybrid Flow
|
|
|
|
if ( $flow eq "hybrid" ) {
|
|
|
|
|
2015-03-23 12:54:22 +01:00
|
|
|
my $access_token;
|
|
|
|
my $id_token;
|
|
|
|
my $at_hash;
|
|
|
|
my $c_hash;
|
2015-03-16 18:00:56 +01:00
|
|
|
|
2015-03-23 12:54:22 +01:00
|
|
|
# Hash level
|
|
|
|
my $alg = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenSignAlg};
|
|
|
|
my ($hash_level) = ( $alg =~ /(?:\w{2})(\d{3})/ );
|
|
|
|
|
|
|
|
# Generate code
|
|
|
|
my $codeSession = $self->getOpenIDConnectSession();
|
|
|
|
my $code = $codeSession->id();
|
|
|
|
|
|
|
|
$self->lmLog( "Generated code: $code", 'debug' );
|
|
|
|
|
|
|
|
# Store data in session
|
|
|
|
$codeSession->update(
|
|
|
|
{
|
|
|
|
redirect_uri => $oidc_request->{'redirect_uri'},
|
|
|
|
scope => $oidc_request->{'scope'},
|
|
|
|
user_session_id => $session_id,
|
|
|
|
_utime => time,
|
|
|
|
nonce => $oidc_request->{'nonce'},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
# Compute hash to store in c_hash
|
|
|
|
$c_hash = $self->createHash( $code, $hash_level );
|
|
|
|
|
|
|
|
if ( $response_type =~ /\btoken\b/ ) {
|
|
|
|
|
|
|
|
# Generate access_token
|
|
|
|
my $accessTokenSession = $self->getOpenIDConnectSession;
|
|
|
|
|
|
|
|
unless ($accessTokenSession) {
|
|
|
|
$self->lmLog(
|
|
|
|
"Unable to create OIDC session for access_token",
|
|
|
|
"error" );
|
2015-03-25 10:11:46 +01:00
|
|
|
$self->returnRedirectError( $oidc_request->{'redirect_uri'},
|
|
|
|
"server_error", undef,
|
|
|
|
undef, $oidc_request->{'state'}, 1 );
|
2015-03-23 12:54:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# Store data in access token
|
|
|
|
$accessTokenSession->update(
|
|
|
|
{
|
|
|
|
scope => $oidc_request->{'scope'},
|
|
|
|
rp => $rp,
|
|
|
|
user_session_id => $session_id,
|
|
|
|
_utime => time,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
$access_token = $accessTokenSession->id;
|
|
|
|
|
|
|
|
$self->lmLog( "Generated access token: $access_token",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# Compute hash to store in at_hash
|
|
|
|
$at_hash = $self->createHash( $access_token, $hash_level );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( $response_type =~ /\bid_token\b/ ) {
|
|
|
|
|
|
|
|
# ID token payload
|
|
|
|
my $id_token_exp = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsIDTokenExpiration};
|
2015-06-18 12:20:15 +02:00
|
|
|
$id_token_exp += time;
|
2015-03-23 12:54:22 +01:00
|
|
|
|
|
|
|
my $id_token_acr =
|
|
|
|
"loa-" . $self->{sessionInfo}->{authenticationLevel};
|
|
|
|
|
2015-04-01 17:45:08 +02:00
|
|
|
my $user_id_attribute =
|
|
|
|
$self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsUserIDAttr}
|
|
|
|
|| $self->{whatToTrace};
|
2015-03-23 12:54:22 +01:00
|
|
|
my $user_id = $self->{sessionInfo}->{$user_id_attribute};
|
|
|
|
|
|
|
|
my $id_token_payload_hash = {
|
|
|
|
iss => $issuer, # Issuer Identifier
|
|
|
|
sub => $user_id, # Subject Identifier
|
|
|
|
aud => [$client_id], # Audience
|
|
|
|
exp => $id_token_exp, # expiration
|
|
|
|
iat => time, # Issued time
|
|
|
|
auth_time => $self->{sessionInfo}->{_lastAuthnUTime}
|
|
|
|
, # Authentication time
|
|
|
|
acr =>
|
|
|
|
$id_token_acr, # Authentication Context Class Reference
|
|
|
|
azp => $client_id, # Authorized party
|
|
|
|
# TODO amr
|
|
|
|
nonce => $oidc_request->{'nonce'} # Nonce
|
|
|
|
};
|
|
|
|
|
|
|
|
$id_token_payload_hash->{'at_hash'} = $at_hash if $at_hash;
|
|
|
|
$id_token_payload_hash->{'c_hash'} = $c_hash if $c_hash;
|
|
|
|
|
|
|
|
# Create ID Token
|
|
|
|
$id_token = $self->createIDToken( $id_token_payload_hash, $rp );
|
|
|
|
|
|
|
|
$self->lmLog( "Generated id token: $id_token", 'debug' );
|
|
|
|
}
|
|
|
|
|
|
|
|
my $expires_in = $self->{oidcRPMetaDataOptions}->{$rp}
|
|
|
|
->{oidcRPMetaDataOptionsAccessTokenExpiration};
|
|
|
|
|
|
|
|
# Build Response
|
2015-04-07 11:04:17 +02:00
|
|
|
my $response_url = $self->buildHybridAuthnResponse(
|
|
|
|
$oidc_request->{'redirect_uri'},
|
2015-03-23 12:54:22 +01:00
|
|
|
$code, $access_token, $id_token, $expires_in,
|
2015-04-07 11:04:17 +02:00
|
|
|
$oidc_request->{'state'},
|
|
|
|
$session_state
|
|
|
|
);
|
2015-03-23 12:54:22 +01:00
|
|
|
|
|
|
|
$self->lmLog( "Redirect user to $response_url", 'debug' );
|
|
|
|
$self->{'urldc'} = $response_url;
|
|
|
|
|
|
|
|
$self->_sub('autoRedirect');
|
2015-03-16 18:00:56 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
$self->lmLog( "No flow has been selected", 'debug' );
|
|
|
|
return PE_OK;
|
2015-01-20 11:04:44 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
# TOKEN
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${token_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect TOKEN URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# This should not happen
|
2015-01-28 14:41:10 +01:00
|
|
|
$self->lmLog(
|
|
|
|
"Token request found on an active SSO session, ignoring it",
|
|
|
|
'error' );
|
2015-01-20 11:04:44 +01:00
|
|
|
$self->returnJSONError("invalid_request");
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-02-04 14:25:13 +01:00
|
|
|
# USERINFO
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${userinfo_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect USERINFO URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# This should not happen
|
|
|
|
$self->lmLog(
|
|
|
|
"UserInfo request found on an active SSO session, ignoring it",
|
|
|
|
'error' );
|
|
|
|
$self->returnJSONError("invalid_request");
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-03-27 15:21:43 +01:00
|
|
|
# JWKS
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${jwks_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect JWKS URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# This should not happen
|
|
|
|
$self->lmLog(
|
|
|
|
"JWKS request found on an active SSO session, ignoring it",
|
|
|
|
'error' );
|
|
|
|
$self->returnJSONError("invalid_request");
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-03-30 17:57:23 +02:00
|
|
|
# REGISTRATION
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${registration_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect REGISTRATION URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# This should not happen
|
|
|
|
$self->lmLog(
|
|
|
|
"Registration request found on an active SSO session, ignoring it",
|
|
|
|
'error'
|
|
|
|
);
|
|
|
|
$self->returnJSONError("invalid_request");
|
|
|
|
|
|
|
|
$self->quit;
|
|
|
|
}
|
|
|
|
|
2015-04-02 18:54:00 +02:00
|
|
|
# END SESSION
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${endsession_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog( "URL $url detected as an OpenID Connect END SESSION URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
# Set hidden fields
|
|
|
|
my $oidc_request = {};
|
|
|
|
foreach my $param (qw/id_token_hint post_logout_redirect_uri state/) {
|
|
|
|
$oidc_request->{$param} = $self->getHiddenFormValue($param)
|
|
|
|
|| $self->param($param);
|
|
|
|
$self->lmLog(
|
|
|
|
"OIDC request parameter $param: " . $oidc_request->{$param},
|
|
|
|
'debug' );
|
|
|
|
$self->setHiddenFormValue( $param, $oidc_request->{$param} );
|
|
|
|
}
|
|
|
|
|
|
|
|
my $post_logout_redirect_uri =
|
|
|
|
$oidc_request->{'post_logout_redirect_uri'};
|
|
|
|
my $state = $oidc_request->{'state'};
|
|
|
|
|
|
|
|
# Ask consent for logout
|
2015-04-03 11:05:36 +02:00
|
|
|
if ( $self->param('confirm') == 1 or $self->param('confirm') == -1 ) {
|
2015-04-02 18:54:00 +02:00
|
|
|
if ( $self->param('confirm') == 1 ) {
|
|
|
|
my $apacheSession = $self->getApacheSession($session_id);
|
|
|
|
$self->_deleteSession($apacheSession);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($post_logout_redirect_uri) {
|
|
|
|
|
|
|
|
# Build Response
|
|
|
|
my $response_url =
|
|
|
|
$self->buildLogoutResponse( $post_logout_redirect_uri,
|
|
|
|
$state );
|
|
|
|
|
|
|
|
$self->lmLog( "Redirect user to $response_url", 'debug' );
|
|
|
|
$self->{'urldc'} = $response_url;
|
|
|
|
|
|
|
|
$self->_sub('autoRedirect');
|
|
|
|
}
|
|
|
|
|
|
|
|
return PE_LOGOUT_OK if $self->param('confirm') == 1;
|
|
|
|
return PE_OK;
|
|
|
|
}
|
|
|
|
|
2015-04-03 11:05:36 +02:00
|
|
|
$self->info('<div class="oidc_logout_message">');
|
|
|
|
$self->info( '<h3>' . $self->msg(PM_OIDC_CONFIRM_LOGOUT) . '</h3>' );
|
2015-04-02 18:54:00 +02:00
|
|
|
$self->info('</div>');
|
|
|
|
$self->{activeTimer} = 0;
|
|
|
|
return PE_CONFIRM;
|
|
|
|
}
|
|
|
|
|
2015-06-01 20:22:36 +02:00
|
|
|
# CHECK SESSION
|
|
|
|
if ( $url_path =~ m#${issuerDBOpenIDConnectPath}${checksession_uri}# ) {
|
|
|
|
|
|
|
|
$self->lmLog(
|
|
|
|
"URL $url detected as an OpenID Connect CHECK SESSION URL",
|
|
|
|
'debug' );
|
|
|
|
|
|
|
|
print $self->header(
|
|
|
|
-type => 'text/html',
|
|
|
|
-access_control_allow_origin => '*'
|
|
|
|
);
|
|
|
|
print $self->start_html(
|
|
|
|
-title => 'Check Session',
|
|
|
|
-script => [
|
|
|
|
{
|
|
|
|
-type => 'text/javascript',
|
|
|
|
-src =>
|
|
|
|
'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/rollups/sha256.js'
|
|
|
|
},
|
|
|
|
{
|
|
|
|
-type => 'text/javascript',
|
|
|
|
-src =>
|
|
|
|
'http://crypto-js.googlecode.com/svn/tags/3.1.2/build/components/enc-base64-min.js'
|
|
|
|
},
|
|
|
|
{ -code => $self->getSessionManagementOPIFrameJS }
|
|
|
|
]
|
|
|
|
);
|
|
|
|
print $self->end_html();
|
|
|
|
$self->quit();
|
|
|
|
}
|
|
|
|
|
2015-01-20 11:04:44 +01:00
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
## @apmethod int issuerLogout()
|
|
|
|
# Do nothing
|
|
|
|
# @return Lemonldap::NG::Portal error code
|
|
|
|
sub issuerLogout {
|
|
|
|
PE_OK;
|
|
|
|
}
|
|
|
|
|
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
=encoding utf8
|
|
|
|
|
|
|
|
Lemonldap::NG::Portal::IssuerDBOpenIDConnect - OpenIDConnect Provider for Lemonldap::NG
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
|
|
|
This is an OpenID Connect provider implementation in LemonLDAP::NG
|
|
|
|
|
|
|
|
=head1 SEE ALSO
|
|
|
|
|
|
|
|
L<Lemonldap::NG::Portal>
|
|
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
|
|
|
=item Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>
|
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
=head1 BUG REPORT
|
|
|
|
|
|
|
|
Use OW2 system to report bug or ask for features:
|
|
|
|
L<http://jira.ow2.org>
|
|
|
|
|
|
|
|
=head1 DOWNLOAD
|
|
|
|
|
|
|
|
Lemonldap::NG is available at
|
|
|
|
L<http://forge.objectweb.org/project/showfiles.php?group_id=274>
|
|
|
|
|
|
|
|
=head1 COPYRIGHT AND LICENSE
|
|
|
|
|
|
|
|
=over
|
|
|
|
|
2016-01-21 22:15:19 +01:00
|
|
|
=item Copyright (C) 2014-2016 by Clement Oudot, E<lt>clem.oudot@gmail.comE<gt>
|
2015-01-20 11:04:44 +01:00
|
|
|
|
|
|
|
=back
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify
|
|
|
|
it under the terms of the GNU General Public License as published by
|
|
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
|
|
any later version.
|
|
|
|
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
GNU General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
|
|
along with this program. If not, see L<http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
=cut
|