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 )