Skip to content

Commit f2421ca

Browse files
committed
Answer a cancelled authentication with access_denied
When the End-User cancels the authentication, the client now gets an access_denied error at its redirect URI, with the request's state and iss, in the request's response mode (RFC 6749 sections 4.1.2.1 and 4.2.2.1), instead of SimpleSAMLphp's error page. A cancel is SimpleSAMLphp's UserAborted, raised through the authentication state by an authentication source or an authentication processing filter which lets the End-User cancel. Every other authentication error keeps SimpleSAMLphp's handling. The FAPI 2.0 "user rejects authentication" test of the OpenID4VCI HAIP plan needs this (HT2 of the HAIP plan). - Every login the module starts for an authorization request, including the re-authentication prompt=login or an expired max_age asks for, gets SimpleSAMLphp's ErrorURL (the authorization endpoint) and the redirect to answer the client at (AuthorizationErrorRedirect) in its state. The processing chain's state gets the same exception handler URL and redirect, and a cancel raised in-request out of processState() is answered directly. - The authorization endpoint hands a request carrying SimpleSAMLphp's exception state id to AuthenticationService::rethrowAuthenticationException(), which answers a cancel with access_denied and throws anything else as it was raised. - A cancel is the response to a pushed authorization request, so it consumes the request first, as a successful response does: one request_uri yields at most one response, and a request_uri consumed meanwhile is refused. - An authentication handed back with an exception clears the session's mark that a login was performed, so that after a failed or cancelled re-authentication the earlier session is not taken for a fresh login. - The redirect keeps the response mode by its value: the form_post one holds the services which render its page, which must not go into the session. ResponseModeInterface gains getMode(), and ResponseModeResolver finds the response mode of a value. - The conformance image gets a test-only module, oidcconformance, whose authentication source extends exampleauth:UserPass: logging in as `cancel` cancels, since SimpleSAMLphp's login page has no cancel button. The proxied introspection harness mounts it too. - Docs: the upgrade guide and the Authentication Processing filters section say how a cancel is answered and how a filter raises one.
1 parent 2866b49 commit f2421ca

31 files changed

Lines changed: 1239 additions & 39 deletions

‎docker/Dockerfile‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ RUN /opt/simplesaml/staging-install.sh
2626
RUN /opt/simplesaml/module-setup.sh
2727
ADD docker/ssp/module_oidc.php /var/simplesamlphp/config/module_oidc.php
2828
ADD docker/ssp/authsources.php /var/simplesamlphp/config/authsources.php
29+
ADD docker/ssp/modules/oidcconformance /var/simplesamlphp/modules/oidcconformance
2930
ADD docker/ssp/config-override.php /var/simplesamlphp/config/config-override.php
3031
# Bring the seeded database up to the schema of the module being built. docker/conformance.sql is a dump
3132
# taken at some earlier schema, so the migrations it has not seen run here and a new migration needs no new

‎docker/docker-compose.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ services:
3636
- ./:/var/simplesamlphp/staging-modules/oidc:ro
3737
- ./docker/ssp/module_oidc.php:/var/simplesamlphp/config/module_oidc.php:ro
3838
- ./docker/ssp/authsources.php:/var/simplesamlphp/config/authsources.php:ro
39+
- ./docker/ssp/modules/oidcconformance:/var/simplesamlphp/modules/oidcconformance:ro
3940
- ./docker/ssp/config-override.php:/var/simplesamlphp/config/config-override.php:ro
4041
- ./docker/apache-override.cf:/etc/apache2/sites-enabled/ssp-override.cf:ro
4142
# Catches the mail the OP sends (docker/ssp/config-override.php) and delivers none of it. The OpenID4VCI

‎docker/proxied-introspection-harness/docker-compose.yml‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ x-op-node: &op-node
2121
- ./docker/proxied-introspection-harness/run-on-start.sh:/opt/simplesaml/run-on-start.sh:ro
2222
- ./docker/proxied-introspection-harness/module_oidc.php:/var/simplesamlphp/config/module_oidc.php:ro
2323
- ./docker/ssp/authsources.php:/var/simplesamlphp/config/authsources.php:ro
24+
- ./docker/ssp/modules/oidcconformance:/var/simplesamlphp/modules/oidcconformance:ro
2425
- ./docker/ssp/config-override.php:/var/simplesamlphp/config/config-override.php:ro
2526
- ./docker/apache-override.cf:/etc/apache2/sites-enabled/ssp-override.cf:ro
2627
healthcheck:

‎docker/ssp/authsources.php‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
'core:AdminPassword',
1010
),
1111

12+
// exampleauth:UserPass, where logging in as `cancel` (any password) cancels the authentication, which the
13+
// OIDC module answers to the client with access_denied; see docker/ssp/modules/oidcconformance.
1214
'example-userpass' => [
13-
'exampleauth:UserPass',
15+
'oidcconformance:CancellableUserPass',
1416
'users' => [
1517
'student:studentpass' => [
1618
'uid' => ['student'],

‎docker/ssp/config-override.php‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
declare(strict_types=1);
44

55
$config['module.enable']['exampleauth'] = true;
6+
// The test-only authentication source of docker/ssp/modules/oidcconformance, which lets a login be cancelled.
7+
$config['module.enable']['oidcconformance'] = true;
68
$config['module.enable']['oidc'] = true;
79
// Have preprod warning enabled (though it may not be installed) to ease authproc redirect testing
810
$config['module.enable']['preprodwarning'] = true;
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\Module\oidcconformance\Auth\Source;
6+
7+
use SimpleSAML\Auth\State;
8+
use SimpleSAML\Error\NoState;
9+
use SimpleSAML\Error\UserAborted;
10+
use SimpleSAML\Module\exampleauth\Auth\Source\UserPass;
11+
12+
/**
13+
* exampleauth:UserPass with a way to cancel, for the conformance image only: SimpleSAMLphp's login page has no
14+
* cancel button, and the conformance suite's browser automation can only fill in and click what a page has. Logging
15+
* in as `cancel`, with any password, stands for the End-User cancelling at the login page: the authentication ends
16+
* with SimpleSAMLphp's UserAborted, raised through the state's exception handler, as an authentication source which
17+
* lets the End-User cancel raises it. The OIDC module answers the client with `access_denied`; the suite's
18+
* "user rejects authentication" test logs in so.
19+
*/
20+
class CancellableUserPass extends UserPass
21+
{
22+
public const string CANCEL_USERNAME = 'cancel';
23+
24+
25+
public static function handleLogin(string $authStateId, string $username, string $password): void
26+
{
27+
if ($username === self::CANCEL_USERNAME) {
28+
$state = State::loadState($authStateId, self::STAGEID);
29+
if ($state === null) {
30+
throw new NoState();
31+
}
32+
33+
State::throwException($state, new UserAborted());
34+
}
35+
36+
parent::handleLogin($authStateId, $username, $password);
37+
}
38+
}

‎docs/3-oidc-configuration.md‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,6 +1210,14 @@ Additional OIDC data in the state:
12101210
- \['Oidc'\]\['RelyingPartyMetadata'\]
12111211
- \['Oidc'\]\['AuthorizationRequestParameters'\]
12121212

1213+
A filter which lets the End-User cancel ends the authentication with
1214+
SimpleSAMLphp's `\SimpleSAML\Error\UserAborted`: thrown from `process()`, or,
1215+
once the filter has sent the End-User to a page of its own, passed with the
1216+
state to `\SimpleSAML\Auth\State::throwException()`. The client is then
1217+
answered with an `access_denied` error at its redirect URI (RFC 6749 sections
1218+
4.1.2.1 and 4.2.2.1). An authentication source which lets the End-User cancel does the
1219+
same. Any other exception is shown on SimpleSAMLphp's error page.
1220+
12131221
Example filter configuration:
12141222

12151223
```php

‎docs/4-oidc-docker.md‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@ docker run --name ssp-oidc-dev \
6666
-e SSP_ADMIN_PASSWORD=secret1 \
6767
--mount type=bind,source="$(pwd)/docker/ssp/module_oidc.php",target=/var/simplesamlphp/config/module_oidc.php,readonly \
6868
--mount type=bind,source="$(pwd)/docker/ssp/authsources.php",target=/var/simplesamlphp/config/authsources.php,readonly \
69+
--mount type=bind,source="$(pwd)/docker/ssp/modules/oidcconformance",target=/var/simplesamlphp/modules/oidcconformance,readonly \
6970
--mount type=bind,source="$(pwd)/docker/ssp/config-override.php",target=/var/simplesamlphp/config/config-override.php,readonly \
7071
--mount type=bind,source="$(pwd)/docker/ssp/oidc_module.crt",target=/var/simplesamlphp/cert/oidc_module.crt,readonly \
7172
--mount type=bind,source="$(pwd)/docker/ssp/oidc_module.key",target=/var/simplesamlphp/cert/oidc_module.key,readonly \

‎docs/6-oidc-upgrade.md‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -876,6 +876,17 @@ the token endpoint),
876876
where its claims used to be read as parameters of the request, superseding
877877
those of the same name. Only the authorization endpoint and the PAR endpoint
878878
take a Request Object.
879+
- An authentication the End-User cancels is now answered to the client with an
880+
`access_denied` error at its redirect URI, with the request's `state` and the
881+
issuer (`iss`), in the request's response mode (RFC 6749 sections 4.1.2.1 and
882+
4.2.2.1), where SimpleSAMLphp's error page was shown before. A cancel is SimpleSAMLphp's
883+
`UserAborted`, which an authentication source or an Authentication Processing
884+
Filter raises when it lets the End-User cancel; none of the modules
885+
SimpleSAMLphp 2.5 ships does. This holds for the login of every flow and for
886+
the re-authentication `prompt=login` or an expired `max_age` asks for, which,
887+
cancelled, leaves the earlier session in place. Any other error of an
888+
authentication is shown as before. See
889+
[Authentication Processing filters](3-oidc-configuration.md#authentication-processing-filters-oidc).
879890
- The underlying `thephpleague/oauth2-server` library has been updated from
880891
v8 to v9
881892
- The requirements on packages `lcobucci/jwt` and `web-token/jwt-framework`

‎src/Controllers/AuthorizationController.php‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
use Psr\Http\Message\ResponseInterface;
1010
use Psr\Http\Message\ServerRequestInterface;
1111
use SimpleSAML\Auth\ProcessingChain;
12+
use SimpleSAML\Auth\State;
1213
use SimpleSAML\Module\oidc\Bridges\PsrHttpBridge;
1314
use SimpleSAML\Module\oidc\Bridges\SspBridge;
1415
use SimpleSAML\Module\oidc\ModuleConfig;
@@ -55,6 +56,13 @@ public function __invoke(ServerRequestInterface $request): ResponseInterface
5556
$state = null;
5657
$this->loggerService->debug('AuthorizationController::invoke: Request parameters: ', $queryParameters);
5758

59+
if (isset($queryParameters[State::EXCEPTION_PARAM])) {
60+
// An authentication which ended with an exception, handed back by SimpleSAMLphp: a cancel is answered to
61+
// the client, anything else thrown as it was.
62+
$this->loggerService->debug('AuthorizationController::invoke: Authentication ended with an exception.');
63+
$this->authenticationService->rethrowAuthenticationException($queryParameters);
64+
}
65+
5866
if (!isset($queryParameters[ProcessingChain::AUTHPARAM])) {
5967
$this->loggerService->debug('AuthorizationController::invoke: No AuthProcId query param.');
6068
$authorizationRequest = $this->authorizationServer->validateAuthorizationRequest($request);

0 commit comments

Comments
 (0)