Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion config/module_oidc.php.dist
Original file line number Diff line number Diff line change
Expand Up @@ -1037,10 +1037,27 @@ $config = [

/**
* (optional) Enable or disable API capabilities. Default is disabled
* (false).
* (false). If API capabilities are enabled, you can enable or disable
* specific API endpoints as needed and set up API tokens to allow
* access to those endpoints. If API capabilities are disabled, all API
* endpoints will be inaccessible regardless of the settings for
* specific endpoints and API tokens.
*
*/
ModuleConfig::OPTION_API_ENABLED => false,

/**
* (optional) API Enable VCI Credential Offer API endpoint. Default is
* disabled (false). Only relevant if API capabilities are enabled.
*/
ModuleConfig::OPTION_API_VCI_CREDENTIAL_OFFER_ENDPOINT_ENABLED => false,

/**
* (optional) API Enable OAuth2 Token Introspection API endpoint. Default
* is disabled (false). Only relevant if API capabilities are enabled.
*/
ModuleConfig::OPTION_API_OAUTH2_TOKEN_INTROSPECTION_ENDPOINT_ENABLED => false,

/**
* List of API tokens which can be used to access API endpoints based on
* given scopes. The format is: ['token' => [ApiScopesEnum]]
Expand All @@ -1050,6 +1067,11 @@ $config = [
// \SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::All, // Gives access to the whole API.
// \SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::VciAll, // Gives access to all VCI-related endpoints.
// \SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::VciCredentialOffer, // Gives access to the credential offer endpoint.
// ],
// 'strong-random-token-string-2' => [
// \SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::All, // Gives access to the whole API.
// \SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::OAuth2All, // Gives access to all OAuth2-related endpoints.
// \SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::OAuth2TokenIntrospection, // Gives access to the token introspection endpoint.
// ],
],
];
3 changes: 1 addition & 2 deletions docker/ssp/config-override.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
'database.dsn' => getenv('DB.DSN') ?: 'sqlite:/var/simplesamlphp/data/mydb.sq3',
'database.username' => getenv('DB.USERNAME') ?: 'user',
'database.password' => getenv('DB.PASSWORD') ?: 'password',
'language.i18n.backend' => 'gettext/gettext',
'logging.level' => 7,
'usenewui' => false,

] + $config;
12 changes: 12 additions & 0 deletions docker/ssp/module_oidc.php
Original file line number Diff line number Diff line change
Expand Up @@ -128,4 +128,16 @@
ModuleConfig::OPTION_PROTOCOL_CACHE_ADAPTER_ARGUMENTS => [
// Use defaults
],

ModuleConfig::OPTION_API_ENABLED => true,

ModuleConfig::OPTION_API_VCI_CREDENTIAL_OFFER_ENDPOINT_ENABLED => true,

ModuleConfig::OPTION_API_OAUTH2_TOKEN_INTROSPECTION_ENDPOINT_ENABLED => true,

ModuleConfig::OPTION_API_TOKENS => [
'strong-random-token-string' => [
\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::All, // Gives access to the whole API.
],
],
];
1 change: 1 addition & 0 deletions docs/1-oidc.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,4 @@ Upgrading? See the [upgrade guide](6-oidc-upgrade.md).
- Conformance tests: [OpenID Conformance](5-oidc-conformance.md)
- Upgrading between versions: [Upgrade guide](6-oidc-upgrade.md)
- Common questions: [FAQ](7-oidc-faq.md)
- API documentation: [API](8-api.md)
13 changes: 13 additions & 0 deletions docs/6-oidc-upgrade.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,15 @@
This is an upgrade guide from versions 1 → 7. Review the changes and
apply those relevant to your deployment.

In general, when upgrading any of the SimpleSAMLphp modules or the
SimpleSAMLphp instance itself, you should clear the SimpleSAMLphp
cache after the upgrade. In newer versions of SimpleSAMLphp, the
following command is available to do that:

```shell
composer clear-symfony-cache
```

## Version 6 to 7

As the database schema has been updated, you will have to run the DB migrations
Expand All @@ -15,6 +24,8 @@ keys for protocol (Connect), Federation, and VCI purposes. This was introduced
to support signature algorithm negotiation with the clients.
- Clients can now be configured with new properties:
- ID Token Signing Algorithm (`id_token_signed_response_alg`)
- Optional OAuth2 Token Introspection endpoint, as per RFC7662. Check the API
documentation for more details.
- Initial support for OpenID for Verifiable Credential Issuance
(OpenID4VCI). Note that the implementation is experimental. You should not use
it in production.
Expand All @@ -34,6 +45,8 @@ key roll-ower scenarios, etc.
- `ModuleConfig::OPTION_TIMESTAMP_VALIDATION_LEEWAY` - optional, used for
setting allowed time tolerance for timestamp validation in artifacts like JWSs.
multiple Federation-related signing algorithms and key pairs.
- `ModuleConfig::OPTION_API_OAUTH2_TOKEN_INTROSPECTION_ENDPOINT_ENABLED` -
optional, enables the OAuth2 token introspection endpoint as per RFC7662.
- Several new options regarding experimental support for OpenID4VCI.

Major impact changes:
Expand Down
119 changes: 117 additions & 2 deletions docs/api.md → docs/8-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ ModuleConfig::OPTION_API_TOKENS => [
Scopes determine which endpoints are accessible by the API access token. The following scopes are available:

* `\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::All`: Access to all endpoints.
* `\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::VciAll`: Access to all VCI-related endpoints
* `\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::VciAll`: Access to all VCI-related endpoints.
* `\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::VciCredentialOffer`: Access to credential offer endpoint.
* `\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::OAuth2All`: Access to all OAuth2-related endpoints.
* `\SimpleSAML\Module\oidc\Codebooks\ApiScopesEnum::OAuth2TokenIntrospection`: Access to the OAuth2 token introspection endpoint.

## API Endpoints

Expand Down Expand Up @@ -142,4 +144,117 @@ Response:
{
"credential_offer_uri": "openid-credential-offer://?credential_offer={\"credential_issuer\":\"https:\\/\\/idp.mivanci.incubator.hexaa.eu\",\"credential_configuration_ids\":[\"ResearchAndScholarshipCredentialDcSdJwt\"],\"grants\":{\"urn:ietf:params:oauth:grant-type:pre-authorized_code\":{\"pre-authorized_code\":\"_ffcdf6d86cd564c300346351dce0b4ccb2fde304e2\",\"tx_code\":{\"input_mode\":\"numeric\",\"length\":4,\"description\":\"Please provide the one-time code that was sent to e-mail testuser@example.com\"}}}}"
}
```
```

### Token Introspection

Enables token introspection for OAuth2 access tokens and refresh tokens as per
[RFC 7662](https://datatracker.ietf.org/doc/html/rfc7662).

#### Path

`/api/oauth2/token-introspection`

#### Method

`POST`

#### Authorization

Access is granted if:
* The client is authenticated using one of the supported OAuth2 client
authentication methods (Basic, Post, Private Key JWT, Bearer).
* Or, if the request is authorized using an API Bearer Token with
the appropriate scope.

#### Request

The request is sent with `application/x-www-form-urlencoded` encoding with the
following parameters:

* __token__ (string, mandatory): The string value of the token.
* __token_type_hint__ (string, optional): A hint about the type of the
token submitted for introspection. Allowed values:
* `access_token`
* `refresh_token`

#### Response

The response is a JSON object with the following fields:

* __active__ (boolean, mandatory): Indicator of whether or not the presented
token is currently active.
* __scope__ (string, optional): A JSON string containing a space-separated
list of scopes associated with this token.
* __client_id__ (string, optional): Client identifier for the OAuth 2.0 client
that requested this token.
* __token_type__ (string, optional): Type of the token as defined in OAuth 2.0.
* __exp__ (integer, optional): Expiration time.
* __iat__ (integer, optional): Issued at time.
* __nbf__ (integer, optional): Not before time.
* __sub__ (string, optional): Subject identifier for the user who
authorized the token.
* __aud__ (string/array, optional): Audience for the token.
* __iss__ (string, optional): Issuer of the token.
* __jti__ (string, optional): Identifier for the token.

If the token is not active, only the `active` field with a value of
`false` is returned.

#### Sample 1

Introspect an active access token using an API Bearer Token.

Request:

```shell
curl --location 'https://idp.mivanci.incubator.hexaa.eu/ssp/module.php/oidc/api/oauth2/token-introspection' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer ***' \
--data-urlencode 'token=access-token-string'
```

Response:

```json
{
"active": true,
"scope": "openid profile email",
"client_id": "test-client",
"token_type": "Bearer",
"exp": 1712662800,
"iat": 1712659200,
"sub": "user-id",
"aud": "test-client",
"iss": "https://idp.mivanci.incubator.hexaa.eu",
"jti": "token-id"
}
```

#### Sample 2

Introspect a refresh token using an API Bearer Token.

Request:

```shell
curl --location 'https://idp.mivanci.incubator.hexaa.eu/ssp/module.php/oidc/api/oauth2/token-introspection' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Bearer ***' \
--data-urlencode 'token=refresh-token-string' \
--data-urlencode 'token_type_hint=refresh_token'
```

Response:

```json
{
"active": true,
"scope": "openid profile",
"client_id": "test-client",
"exp": 1715251200,
"sub": "user-id",
"aud": "test-client",
"jti": "refresh-token-id"
}
```
3 changes: 0 additions & 3 deletions docs/index.md

This file was deleted.

7 changes: 7 additions & 0 deletions routing/routes/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use SimpleSAML\Module\oidc\Controllers\Federation\EntityStatementController;
use SimpleSAML\Module\oidc\Controllers\JwksController;
use SimpleSAML\Module\oidc\Controllers\OAuth2\OAuth2ServerConfigurationController;
use SimpleSAML\Module\oidc\Controllers\OAuth2\TokenIntrospectionController;
use SimpleSAML\Module\oidc\Controllers\UserInfoController;
use SimpleSAML\Module\oidc\Controllers\VerifiableCredentials\CredentialIssuerConfigurationController;
use SimpleSAML\Module\oidc\Controllers\VerifiableCredentials\CredentialIssuerCredentialController;
Expand Down Expand Up @@ -142,4 +143,10 @@
RoutesEnum::ApiVciCredentialOffer->value,
)->controller([VciCredentialOfferApiController::class, 'credentialOffer'])
->methods([HttpMethodsEnum::POST->value]);

$routes->add(
RoutesEnum::ApiOAuth2TokenIntrospection->name,
RoutesEnum::ApiOAuth2TokenIntrospection->value,
)->controller(TokenIntrospectionController::class)
->methods([HttpMethodsEnum::POST->value]);
};
1 change: 1 addition & 0 deletions routing/services/services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ services:
SimpleSAML\Module\oidc\Utils\RequestParamsResolver: ~
SimpleSAML\Module\oidc\Utils\ClassInstanceBuilder: ~
SimpleSAML\Module\oidc\Utils\JwksResolver: ~
SimpleSAML\Module\oidc\Utils\AuthenticatedOAuth2ClientResolver: ~
SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor:
factory: ['@SimpleSAML\Module\oidc\Factories\ClaimTranslatorExtractorFactory', 'build']
SimpleSAML\Module\oidc\Utils\FederationCache:
Expand Down
66 changes: 66 additions & 0 deletions src/Bridges/OAuth2Bridge.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<?php

declare(strict_types=1);

namespace SimpleSAML\Module\oidc\Bridges;

use Defuse\Crypto\Crypto;
use Defuse\Crypto\Key;
use SimpleSAML\Module\oidc\Exceptions\OidcException;
use SimpleSAML\Module\oidc\ModuleConfig;

class OAuth2Bridge
{
public function __construct(
protected readonly ModuleConfig $moduleConfig,
) {
}

/**
* Bridge `encrypt` function, which can be used instead of
* \League\OAuth2\Server\CryptTrait::encrypt()
*
* @param string $unencryptedData
* @param Key|string $encryptionKey
* @return string
* @throws OidcException
*/
public function encrypt(
string $unencryptedData,
null|Key|string $encryptionKey = null,
): string {
$encryptionKey ??= $this->moduleConfig->getEncryptionKey();

try {
return $encryptionKey instanceof Key ?
Crypto::encrypt($unencryptedData, $encryptionKey) :
Crypto::encryptWithPassword($unencryptedData, $encryptionKey);
} catch (\Exception $e) {
throw new OidcException('Error encrypting data: ' . $e->getMessage(), (int)$e->getCode(), $e);
}
}

/**
* Bridge `decrypt` function, which can be used instead of
* \League\OAuth2\Server\CryptTrait::decrypt()
*
* @param string $encryptedData
* @param Key|string $encryptionKey
* @return string
* @throws OidcException
*/
public function decrypt(
string $encryptedData,
null|Key|string $encryptionKey = null,
): string {
$encryptionKey ??= $this->moduleConfig->getEncryptionKey();

try {
return $encryptionKey instanceof Key ?
Crypto::decrypt($encryptedData, $encryptionKey) :
Crypto::decryptWithPassword($encryptedData, $encryptionKey);
} catch (\Exception $e) {
throw new OidcException('Error decrypting data: ' . $e->getMessage(), (int)$e->getCode(), $e);
}
}
}
4 changes: 4 additions & 0 deletions src/Codebooks/ApiScopesEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,8 @@ enum ApiScopesEnum: string
// Verifiable Credential Issuance related scopes.
case VciAll = 'vci_all'; // Gives access to all VCI-related endpoints.
case VciCredentialOffer = 'vci_credential_offer'; // Gives access to the credential offer endpoint.

// OAuth2 related scopes.
case OAuth2All = 'oauth2_all'; // Gives access to all OAuth2-related endpoints.
case OAuth2TokenIntrospection = 'oauth2_token_introspection'; // Gives access to the token introspection endpoint.
}
1 change: 1 addition & 0 deletions src/Codebooks/RoutesEnum.php
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,5 @@ enum RoutesEnum: string
****************************************************************************************************************/

case ApiVciCredentialOffer = 'api/vci/credential-offer';
case ApiOAuth2TokenIntrospection = 'api/oauth2/token-introspection';
}
6 changes: 6 additions & 0 deletions src/Controllers/Api/VciCredentialOfferApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,15 @@ public function __construct(
}

/**
* @throws OidcServerException
*/
public function credentialOffer(Request $request): Response
{
if (!$this->moduleConfig->getApiVciCredentialOfferEndpointEnabled()) {
$this->loggerService->warning('Credential Offer API endpoint not enabled.');
throw OidcServerException::forbidden('Credential Offer API endpoint not enabled.');
}

$this->loggerService->debug('VciCredentialOfferApiController::credentialOffer');

$this->loggerService->debug(
Expand Down
Loading
Loading