Lemonldap-NG-Portal
view release on metacpan or search on metacpan
lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm view on Meta::CPAN
# DEPRECATED, remove in 3.0, use sendOidcResponse instead
# @param redirect_uri Redirect URI
# @param code Code
# @param access_token Access token
# @param id_token ID token
# @param expires_in Expiration of access token
# @param state State
# @param session_state Session state
# return String Authentication Response URI
sub buildHybridAuthnResponse {
my (
$self, $redirect_uri, $code, $access_token, $id_token,
$expires_in, $state, $session_state, $scope
) = @_;
return $self->getFragmentResponse(
$redirect_uri,
{
code => $code,
(
$access_token
? ( token_type => 'bearer', access_token => $access_token )
: ()
),
(
$id_token ? ( id_token => $id_token )
: ()
),
( $expires_in ? ( expires_in => $expires_in ) : () ),
( $state ? ( state => $state ) : () ),
( $scope ? ( scope => $scope ) : () ),
( $session_state ? ( session_state => $session_state ) : () )
}
);
}
sub getAccessTokenFromTokenEndpoint {
my ( $self, $req, $op, $grant_type, $grant_options ) = @_;
$grant_options ||= {};
my $client_id = $self->opOptions->{$op}->{oidcOPMetaDataOptionsClientID};
my $client_secret =
$self->opOptions->{$op}->{oidcOPMetaDataOptionsClientSecret};
my $access_token_uri =
$self->opMetadata->{$op}->{conf}->{token_endpoint};
unless ($access_token_uri) {
$self->logger->error(
"Could not build Token request: no
'token_endpoint'" . " in JSON metadata for OP $op"
);
return 0;
}
my $auth_method =
$self->opOptions->{$op}->{oidcOPMetaDataOptionsTokenEndpointAuthMethod}
|| 'client_secret_post';
unless ( $auth_method =~
/^(?:client_secret_(?:(?:pos|jw)t|basic)|private_key_jwt)$/ )
{
$self->logger->error(
"Bad authentication method on token endpoint for OP $op");
return 0;
}
$self->logger->debug(
"Using auth method $auth_method to token endpoint $access_token_uri");
my $response;
my $token_request_params = {
grant_type => $grant_type,
%{$grant_options}
};
# Call oidcGenerateTokenRequest
my $h = $self->p->processHook( $req, 'oidcGenerateTokenRequest',
$op, $token_request_params );
return 0 if ( $h != PE_OK );
if ( $auth_method eq "client_secret_basic" ) {
$response = $self->ua->post(
$access_token_uri, $token_request_params,
"Authorization" => "Basic "
. encode_base64( "$client_id:$client_secret", '' ),
"Content-Type" => 'application/x-www-form-urlencoded',
);
}
else {
if ( $auth_method eq "client_secret_post" ) {
$token_request_params->{client_id} = $client_id;
$token_request_params->{client_secret} = $client_secret;
}
elsif ( $auth_method =~ /^(?:client_secret|private_key)_jwt$/ ) {
my $alg = $self->opOptions->{$op}
->{oidcOPMetaDataOptionsTokenEndpointAuthSigAlg};
if ( !$alg ) {
my $default_signing_key_type =
$self->_getKeyType(
$self->get_public_key("default-oidc-sig") );
$alg =
$auth_method eq 'client_secret_jwt' ? 'HS256'
: $default_signing_key_type eq 'EC' ? 'ES256'
: 'RS256';
}
my $time = time;
my $jws = $self->createJWTForOP( {
iss => $client_id,
sub => $client_id,
aud => $access_token_uri,
jti => $self->generateNonce,
exp => $time + $self->conf->{shortTokenTTL},
iat => $time,
},
$alg, $op
);
$token_request_params->{client_id} = $client_id;
$token_request_params->{client_assertion_type} =
'urn:ietf:params:oauth:client-assertion-type:jwt-bearer';
$token_request_params->{client_assertion} = $jws;
}
else {
$self->logger->error("Unknown auth method $auth_method");
}
$response = $self->ua->post( $access_token_uri, $token_request_params,
"Content-Type" => 'application/x-www-form-urlencoded' );
}
$self->logger->debug(
"Token request sent: " . $response->request()->as_string );
if ( $response->is_error ) {
$self->logger->error(
"Bad token response from $op, grant_type: $grant_type, error: "
. $response->message );
$self->logger->debug(
"Token response received: " . $response->as_string );
return 0;
}
return $response->decoded_content;
}
# Get Token response with authorization code
# @param op OpenIP Provider configuration key
# @param code Code
# @param auth_method Authentication Method (optional)
# return String Token response decoded content
sub getAuthorizationCodeAccessToken {
my ( $self, $req, $op, $code ) = @_;
my $redirect_uri = $self->getCallbackUri($req);
lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm view on Meta::CPAN
eval {
( $client_id, $client_secret ) =
split( ':', decode_base64($1), 2 );
};
$self->logger->error("Bad authentication header: $@") if ($@);
# Unescape clientId and clientSecret to be compliant with RFC 6749
# https://datatracker.ietf.org/doc/html/rfc6749
$client_id = uri_unescape($client_id);
$client_secret = uri_unescape($client_secret);
# Using multiple methods is an error
if (
( $req->param('client_id') and $req->param('client_secret') )
or ( $req->param('client_assertion')
and $req->param('client_assertion_type') )
)
{
$self->logger->error("Multiple client authentication methods used");
( $client_id, $client_secret ) = ( undef, undef );
}
}
# JWS authentication
elsif ( my $atype = $req->param('client_assertion_type')
and my $jws = $req->param('client_assertion')
and my $_clientId = $req->param('client_id') )
{
# Type must be 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer'
if (
$atype eq 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer' )
{
# JWS token must contain iss, sub and iss must be equal to usb
my $payload = getJWTPayload($jws);
if ( $payload
and ( ref($payload) eq 'HASH' )
and $payload->{iss}
and $payload->{sub}
and $payload->{iss} eq $payload->{sub}
and $payload->{iss} eq $_clientId )
{
# client_id must match to a known relying party
my $rp = $self->getRP($_clientId);
if ($rp) {
# RP must have a signature key registered
# (key may be the secret for HS* alg)
if ( $self->rpSigKey->{$rp}
or $self->rpOptions->{$rp}
->{oidcRPMetaDataOptionsClientSecret} )
{
# Signature must be valid
my ( $jwt, $alg ) =
$self->decodeJWT( $jws, undef, $rp );
if ($jwt) {
$scheme =
$alg =~ /^HS/i
? 'client_secret_jwt'
: 'private_key_jwt';
# Token must be time-valid
if ( $jwt->{aud} and $jwt->{exp} ) {
if ( time < $jwt->{exp} ) {
$self->logger->debug("JWS is valid");
# Then export the client_id !
$client_id = $_clientId;
}
else {
$self->logger->error('JWS expired');
}
}
else {
$self->logger->error(
'Bad JWS content (missing aud or exp)');
}
}
else {
$self->logger->error('Bad JWS signature');
}
}
else {
$self->logger->error("No signature key found for $rp");
}
}
else {
$self->logger->error(
"Unable to find any RP with client_id=$_clientId");
}
}
else {
$self->logger->error("Bad JWS payload: $jws");
}
}
else {
$self->logger->error("Unsuported client_assertion_type $atype");
}
}
elsif ( $req->param('client_id')
and $req->body_parameters->{client_secret} )
{
$scheme = 'client_secret_post';
$self->logger->debug("Method client_secret_post used");
$client_id = $req->param('client_id');
$client_secret = $req->param('client_secret');
}
elsif ( $req->param('client_id') ) {
$scheme = 'none';
$self->logger->debug("Method none used");
$client_id = $req->param('client_id');
}
return ( $client_id, $client_secret, $scheme );
}
# Get Access Token
# @return access_token
sub getEndPointAccessToken {
my ( $self, $req ) = @_;
lib/Lemonldap/NG/Portal/Lib/OpenIDConnect.pm view on Meta::CPAN
my $extra_headers = $extra_headers_or_type;
# Compatibility with old signature
if ( !ref($extra_headers_or_type) and $extra_headers_or_type ) {
$extra_headers = { typ => $extra_headers_or_type };
}
return $self->_createJWT( $payload, $alg, $rp, $extra_headers );
}
sub createJWTForOP {
my ( $self, $payload, $alg, $op, $extra_headers_or_type ) = @_;
my $extra_headers = $extra_headers_or_type;
# Compatibility with old signature
if ( !ref($extra_headers_or_type) and $extra_headers_or_type ) {
$extra_headers = { typ => $extra_headers_or_type };
}
return $self->_createJWT( $payload, $alg, $op, $extra_headers, 1 );
}
sub _createJWT {
my ( $self, $payload, $alg, $partner, $extra_headers, $isRp ) = @_;
my @keyArg;
$extra_headers //= {};
# Set Cript::JWT arguments depending on "alg"
# a) "none"
if ( $alg eq 'none' ) {
@keyArg = ( allow_none => 1 );
}
# b) HMAC algorithms, key is the client secret
elsif ( $alg =~ /^HS/ ) {
# Sign with client secret
my $client_secret =
$isRp
? $self->opOptions->{$partner}->{oidcOPMetaDataOptionsClientSecret}
: $self->rpOptions->{$partner}->{oidcRPMetaDataOptionsClientSecret};
unless ($client_secret) {
$self->logger->error(
"Algorithm $alg needs a Client Secret to sign JWT");
return;
}
@keyArg = ( key => $client_secret );
}
# c) asymetric algorithms
else {
my $key_list =
$isRp
? $self->opOptions->{$partner}->{oidcOPMetaDataOptionsSigningKey}
: $self->rpOptions->{$partner}->{oidcRPMetaDataOptionsSigningKey};
$key_list ||= $self->conf->{oidcServiceSignatureKey};
my $key_id = ( split( /\s*,\s*/, $key_list ) )[0];
my $priv_key = $self->get_private_key($key_id);
unless ($priv_key) {
$self->logger->error(
"Algorithm $alg needs a Private Key to sign JWT");
return;
}
my $key_type = $self->_getKeyType($priv_key);
my $required_key_type = $self->_getRequiredKeyTypeForAlg($alg);
if ( $required_key_type and $key_type ne $required_key_type ) {
$self->logger->error(
"Algorithm $alg needs a $required_key_type key to sign JWT,"
. " but key $key_id is $key_type" );
return;
}
@keyArg = ( key => \$priv_key->{private}, );
if ( $priv_key->{external_id} ) {
$extra_headers->{kid} = $priv_key->{external_id};
}
my $key_info = $self->getCertInfo( $priv_key->{public} );
if ( $key_info->{x5t} ) {
$extra_headers->{x5t} = $key_info->{x5t};
}
}
my $noTyp =
$isRp
? $self->opOptions->{$partner}->{oidcOPMetaDataOptionsNoJwtHeader}
: $self->rpOptions->{$partner}->{oidcRPMetaDataOptionsNoJwtHeader};
if ($noTyp) {
delete $extra_headers->{typ};
}
else {
$extra_headers->{typ} ||= 'JWT';
}
# Encode payload here due to #2748
my $jwt = eval {
encode_jwt(
payload => to_json($payload),
alg => $alg,
extra_headers => $extra_headers,
@keyArg,
);
};
if ($@) {
$self->logger->error("Unable to build JWT: $@");
return;
}
return $jwt;
}
# Return ID Token
# @param payload ID Token content
# @param rp Internal Relying Party identifier
# @return String id_token ID Token as JWT
sub createIDToken {
my ( $self, $req, $payload, $rp, $sessionData ) = @_;
( run in 0.977 second using v1.01-cache-2.11-cpan-302cb4679cc )