Skip to content

Commit e87548e

Browse files
committed
Introduce LogoutToken abstraction
1 parent 0cd0d77 commit e87548e

File tree

5 files changed

+243
-0
lines changed

5 files changed

+243
-0
lines changed

src/Codebooks/ClaimsEnum.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,8 @@ enum ClaimsEnum: string
158158
// EncryptionValuesSupported
159159
case EncValuesSupported = 'enc_values_supported';
160160

161+
case Events = 'events';
162+
161163
case Evidence = 'evidence';
162164

163165
// ExpirationTime

src/Core.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum;
1212
use SimpleSAML\OpenID\Core\Factories\ClientAssertionFactory;
1313
use SimpleSAML\OpenID\Core\Factories\IdTokenFactory;
14+
use SimpleSAML\OpenID\Core\Factories\LogoutTokenFactory;
1415
use SimpleSAML\OpenID\Core\Factories\RequestObjectFactory;
1516
use SimpleSAML\OpenID\Decorators\DateIntervalDecorator;
1617
use SimpleSAML\OpenID\Factories\AlgorithmManagerDecoratorFactory;
@@ -58,6 +59,8 @@ class Core
5859

5960
protected ?AlgorithmManagerDecorator $algorithmManagerDecorator = null;
6061

62+
protected ?LogoutTokenFactory $logoutTokenFactory = null;
63+
6164

6265
public function __construct(
6366
protected readonly SupportedAlgorithms $supportedAlgorithms = new SupportedAlgorithms(
@@ -229,4 +232,18 @@ public function claimFactory(): ClaimFactory
229232
$this->helpers(),
230233
);
231234
}
235+
236+
237+
public function logoutTokenFactory(): LogoutTokenFactory
238+
{
239+
return $this->logoutTokenFactory ??= new LogoutTokenFactory(
240+
$this->jwsDecoratorBuilder(),
241+
$this->jwsVerifierDecorator(),
242+
$this->jwksDecoratorFactory(),
243+
$this->jwsSerializerManagerDecorator(),
244+
$this->timestampValidationLeewayDecorator,
245+
$this->helpers(),
246+
$this->claimFactory(),
247+
);
248+
}
232249
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\OpenID\Core\Factories;
6+
7+
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum;
8+
use SimpleSAML\OpenID\Core\LogoutToken;
9+
use SimpleSAML\OpenID\Jwk\JwkDecorator;
10+
use SimpleSAML\OpenID\Jws\Factories\ParsedJwsFactory;
11+
12+
class LogoutTokenFactory extends ParsedJwsFactory
13+
{
14+
public function fromToken(string $token): LogoutToken
15+
{
16+
return new LogoutToken(
17+
$this->jwsDecoratorBuilder->fromToken($token),
18+
$this->jwsVerifierDecorator,
19+
$this->jwksDecoratorFactory,
20+
$this->jwsSerializerManagerDecorator,
21+
$this->timestampValidationLeeway,
22+
$this->helpers,
23+
$this->claimFactory,
24+
);
25+
}
26+
27+
28+
/**
29+
* @param array<non-empty-string,mixed> $payload
30+
* @param array<non-empty-string,mixed> $header
31+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
32+
*/
33+
public function fromData(
34+
JwkDecorator $signingKey,
35+
SignatureAlgorithmEnum $signatureAlgorithm,
36+
array $payload,
37+
array $header,
38+
): LogoutToken {
39+
return new LogoutToken(
40+
$this->jwsDecoratorBuilder->fromData(
41+
$signingKey,
42+
$signatureAlgorithm,
43+
$payload,
44+
$header,
45+
),
46+
$this->jwsVerifierDecorator,
47+
$this->jwksDecoratorFactory,
48+
$this->jwsSerializerManagerDecorator,
49+
$this->timestampValidationLeeway,
50+
$this->helpers,
51+
$this->claimFactory,
52+
);
53+
}
54+
}

src/Core/LogoutToken.php

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\OpenID\Core;
6+
7+
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
8+
use SimpleSAML\OpenID\Codebooks\UriPattern;
9+
use SimpleSAML\OpenID\Exceptions\LogoutTokenException;
10+
use SimpleSAML\OpenID\Jws\ParsedJws;
11+
12+
/**
13+
* Logout Token abstraction from
14+
* https://openid.net/specs/openid-connect-backchannel-1_0.html#LogoutToken
15+
*/
16+
class LogoutToken extends ParsedJws
17+
{
18+
public function getIssuer(): string
19+
{
20+
// REQUIRED. Issuer Identifier, as specified in Section 2 of
21+
// [OpenID.Core].
22+
$iss = parent::getIssuer() ?? throw new LogoutTokenException('No Issuer claim found.');
23+
24+
// We will leave the possibility of http usage for local testing purposes.
25+
return $this->helpers->type()->enforceUri($iss, 'Issuer claim', UriPattern::HttpNoQueryNoFragment->value);
26+
}
27+
28+
29+
public function getAudience(): array
30+
{
31+
// REQUIRED. Audience(s), as specified in Section 2 of [OpenID.Core].
32+
return parent::getAudience() ?? throw new LogoutTokenException('No Audience claim found.');
33+
}
34+
35+
36+
public function getIssuedAt(): int
37+
{
38+
// REQUIRED. Issued at time, as specified in Section 2 of [OpenID.Core].
39+
return parent::getIssuedAt() ?? throw new LogoutTokenException('No Issued At claim found.');
40+
}
41+
42+
43+
public function getExpirationTime(): int
44+
{
45+
// REQUIRED. Expiration time, as specified in Section 2 of
46+
// [OpenID.Core].
47+
return parent::getExpirationTime() ?? throw new LogoutTokenException('No Expiration Time claim found.');
48+
}
49+
50+
51+
public function getJwtId(): string
52+
{
53+
// REQUIRED. Unique identifier for the token, as specified in Section 9
54+
// of [OpenID.Core].
55+
return parent::getJwtId() ?? throw new LogoutTokenException('No JWT ID claim found.');
56+
}
57+
58+
59+
/**
60+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
61+
* @throws \SimpleSAML\OpenID\Exceptions\LogoutTokenException
62+
* @return mixed[]
63+
*/
64+
public function getEvents(): array
65+
{
66+
// REQUIRED. Claim whose value is a JSON object containing the member
67+
// name http://schemas.openid.net/event/backchannel-logout. This
68+
// declares that the JWT is a Logout Token. The corresponding member
69+
// value MUST be a JSON object and SHOULD be the empty JSON object {}.
70+
$events = $this->getPayloadClaim(ClaimsEnum::Events->value);
71+
72+
if (is_null($events)) {
73+
throw new LogoutTokenException('No Events claim found.');
74+
}
75+
76+
if (
77+
(!is_array($events)) ||
78+
(!array_key_exists('http://schemas.openid.net/event/backchannel-logout', $events))
79+
) {
80+
throw new LogoutTokenException('Malformed events claim.');
81+
}
82+
83+
return $events;
84+
}
85+
86+
87+
/**
88+
* @return ?non-empty-string
89+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
90+
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
91+
*/
92+
public function getSessionId(): ?string
93+
{
94+
// OPTIONAL. Session ID - String identifier for a Session. This
95+
// represents a Session of a User Agent or device for a logged-in
96+
// End-User at an RP. Different sid values are used to identify
97+
// distinct sessions at an OP. The sid value need only be unique in
98+
// the context of a particular issuer. Its contents are opaque to the
99+
// RP. Its syntax is the same as an OAuth 2.0 Client Identifier.
100+
101+
$sid = $this->getPayloadClaim(ClaimsEnum::Sid->value);
102+
103+
if (is_null($sid)) {
104+
return null;
105+
}
106+
107+
return $this->helpers->type()->ensureNonEmptyString($sid);
108+
}
109+
110+
111+
/**
112+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
113+
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
114+
*/
115+
public function getNonce(): null
116+
{
117+
$nonce = $this->getPayloadClaim(ClaimsEnum::Nonce->value);
118+
119+
if (!is_null($nonce)) {
120+
throw new LogoutTokenException('Nonce claim is forbidden in Logout Token.');
121+
}
122+
123+
return null;
124+
}
125+
126+
127+
/**
128+
* @throws \SimpleSAML\OpenID\Exceptions\LogoutTokenException
129+
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
130+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
131+
*/
132+
protected function validateSubOrSidOrBoth(): void
133+
{
134+
if (
135+
is_null($this->getSubject()) &&
136+
is_null($this->getSessionId())
137+
) {
138+
throw new LogoutTokenException('Missing Subject and Session ID claim in Logout Token.');
139+
}
140+
}
141+
142+
143+
/**
144+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
145+
*/
146+
protected function validate(): void
147+
{
148+
$this->validateByCallbacks(
149+
$this->getIssuer(...),
150+
$this->getAudience(...),
151+
$this->getIssuedAt(...),
152+
$this->getExpirationTime(...),
153+
$this->getJwtId(...),
154+
$this->getEvents(...),
155+
$this->getSessionId(...),
156+
$this->getNonce(...),
157+
$this->validateSubOrSidOrBoth(...),
158+
$this->getAlgorithm(...),
159+
);
160+
}
161+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace SimpleSAML\OpenID\Exceptions;
6+
7+
class LogoutTokenException extends JwsException
8+
{
9+
}

0 commit comments

Comments
 (0)