Skip to content

Commit 97dc0c6

Browse files
committed
WIP
1 parent fee9631 commit 97dc0c6

10 files changed

Lines changed: 230 additions & 38 deletions

File tree

src/Controllers/EndSessionController.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@ public function __invoke(ServerRequestInterface $request): Response
6565
(string)$idTokenHint->claims()->get('sid');
6666
}
6767

68+
$this->loggerService->debug('EndSession: ID Token Hint Session ID: ' . $sidClaim ?? 'N/A');
69+
6870
// Check if RP is requesting logout for session that previously existed (not this current session).
6971
// Claim 'sid' from 'id_token_hint' logout parameter indicates for which session should log out be
7072
// performed (sid is session ID used when ID token was issued during authn). If the requested
@@ -73,19 +75,31 @@ public function __invoke(ServerRequestInterface $request): Response
7375
$sidClaim !== null &&
7476
$this->sessionService->getCurrentSession()->getSessionId() !== $sidClaim
7577
) {
78+
$this->loggerService->debug('Not current session: ' . $sidClaim);
7679
try {
7780
if (($sidSession = $this->sessionService->getSessionById($sidClaim)) !== null) {
81+
$this->loggerService->debug('Found session for ID: ' . $sidClaim);
7882
$sidSessionValidAuthorities = $sidSession->getAuthorities();
7983

8084
if (! empty($sidSessionValidAuthorities)) {
85+
$this->loggerService->debug(
86+
'Valid session authorities: ' . implode(', ', $sidSessionValidAuthorities),
87+
);
8188
$wasLogoutActionCalled = true;
8289
// Create a SessionLogoutTicket so that the sid is available in the static logoutHandler()
8390
$this->sessionLogoutTicketStoreBuilder->getInstance()->add($sidClaim);
8491
// Initiate logout for every valid auth source for the requested session.
8592
foreach ($sidSessionValidAuthorities as $authSourceId) {
93+
$this->loggerService->debug(
94+
'Initiating logout for auth source ID: ' . $authSourceId,
95+
);
8696
$sidSession->doLogout($authSourceId);
8797
}
98+
} else {
99+
$this->loggerService->debug('Session authorities not found for ID: ' . $sidClaim);
88100
}
101+
} else {
102+
$this->loggerService->debug('Session not found for ID: ' . $sidClaim);
89103
}
90104
} catch (Throwable $exception) {
91105
$this->loggerService->warning(
@@ -96,13 +110,19 @@ public function __invoke(ServerRequestInterface $request): Response
96110

97111
$currentSessionValidAuthorities = $this->sessionService->getCurrentSession()->getAuthorities();
98112
if (!empty($currentSessionValidAuthorities)) {
113+
$this->loggerService->debug(
114+
'Current session authorities: ' . implode(', ', $currentSessionValidAuthorities),
115+
);
99116
$wasLogoutActionCalled = true;
100117
// Initiate logout for every valid auth source for the current session.
101118
foreach ($this->sessionService->getCurrentSession()->getAuthorities() as $authSourceId) {
102119
$this->sessionService->getCurrentSession()->doLogout($authSourceId);
103120
}
121+
} else {
122+
$this->loggerService->debug('Current session authorities not found for ID: ' . $sidClaim);
104123
}
105124

125+
$this->loggerService->debug('Was logout action called: ' . var_export($wasLogoutActionCalled, true));
106126
// Set indication for OIDC initiated logout back to false, so that the logoutHandler() method does not
107127
// run for other logout initiated actions, like (currently) re-authentication...
108128
$this->sessionService->setIsOidcInitiatedLogout(false);
@@ -189,14 +209,32 @@ public static function logoutHandler(): void
189209
protected function resolveResponse(LogoutRequest $logoutRequest, bool $wasLogoutActionCalled): Response
190210
{
191211
if (($postLogoutRedirectUri = $logoutRequest->getPostLogoutRedirectUri()) !== null) {
212+
$this->loggerService->debug(
213+
'Logout request includes post-logout redirect URI: ' . $postLogoutRedirectUri,
214+
);
215+
192216
if ($logoutRequest->getState() !== null) {
217+
$this->loggerService->debug(
218+
'Appending logout request state: ' . $logoutRequest->getState(),
219+
);
193220
$postLogoutRedirectUri .= (!str_contains($postLogoutRedirectUri, '?')) ? '?' : '&';
194221
$postLogoutRedirectUri .= http_build_query(['state' => $logoutRequest->getState()]);
222+
} else {
223+
$this->loggerService->debug(
224+
'No state provided for post logout',
225+
);
195226
}
196227

228+
$this->loggerService->debug(
229+
'Final post logout redirect URI: ' . $postLogoutRedirectUri,
230+
);
197231
return new RedirectResponse($postLogoutRedirectUri);
198232
}
199233

234+
$this->loggerService->debug(
235+
'No post logout redirect URI provided for logout. Showing template.',
236+
);
237+
200238
return $this->templateFactory->build(
201239
templateName: 'oidc:/logout.twig',
202240
data: [

src/Factories/CoreFactory.php

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,7 @@
66

77
use SimpleSAML\Module\oidc\ModuleConfig;
88
use SimpleSAML\Module\oidc\Services\LoggerService;
9-
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmBag;
10-
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum;
119
use SimpleSAML\OpenID\Core;
12-
use SimpleSAML\OpenID\SupportedAlgorithms;
1310

1411
class CoreFactory
1512
{
@@ -25,22 +22,8 @@ public function __construct(
2522
*/
2623
public function build(): Core
2724
{
28-
$supportedAlgorithms = new SupportedAlgorithms(
29-
new SignatureAlgorithmBag(
30-
SignatureAlgorithmEnum::from($this->moduleConfig->getFederationSigner()->algorithmId()),
31-
SignatureAlgorithmEnum::RS384,
32-
SignatureAlgorithmEnum::RS512,
33-
SignatureAlgorithmEnum::ES256,
34-
SignatureAlgorithmEnum::ES384,
35-
SignatureAlgorithmEnum::ES512,
36-
SignatureAlgorithmEnum::PS256,
37-
SignatureAlgorithmEnum::PS384,
38-
SignatureAlgorithmEnum::PS512,
39-
),
40-
);
41-
4225
return new Core(
43-
supportedAlgorithms: $supportedAlgorithms,
26+
supportedAlgorithms: $this->moduleConfig->getSupportedAlgorithms(),
4427
logger: $this->loggerService,
4528
);
4629
}

src/Server/Associations/Interfaces/RelyingPartyAssociationInterface.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,17 @@ public function getSessionId(): ?string;
1414
public function setSessionId(?string $sessionId): void;
1515
public function getBackChannelLogoutUri(): ?string;
1616
public function setBackChannelLogoutUri(?string $backChannelLogoutUri): void;
17+
18+
/**
19+
* Get id_token_signed_response_alg metadata parameter used by the client.
20+
*
21+
* @return string|null
22+
*/
23+
public function getClientIdTokenSignedResponseAlg(): ?string;
24+
25+
/**
26+
* Set id_token_signed_response_alg metadata parameter used by the client.
27+
* @param string|null $idTokenSignedResponseAlg
28+
*/
29+
public function setClientIdTokenSignedResponseAlg(?string $idTokenSignedResponseAlg): void;
1730
}

src/Server/Associations/RelyingPartyAssociation.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public function __construct(
1616
* Registered back-channel logout URI for the client.
1717
*/
1818
protected ?string $backChannelLogoutUri = null,
19+
protected ?string $idTokenSignedResponseAlg = null,
1920
) {
2021
}
2122

@@ -58,4 +59,14 @@ public function setBackChannelLogoutUri(?string $backChannelLogoutUri): void
5859
{
5960
$this->backChannelLogoutUri = $backChannelLogoutUri;
6061
}
62+
63+
public function getClientIdTokenSignedResponseAlg(): ?string
64+
{
65+
return $this->idTokenSignedResponseAlg;
66+
}
67+
68+
public function setClientIdTokenSignedResponseAlg(?string $idTokenSignedResponseAlg): void
69+
{
70+
$this->idTokenSignedResponseAlg = $idTokenSignedResponseAlg;
71+
}
6172
}

src/Server/Grants/ImplicitGrant.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -261,7 +261,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz
261261
$responseParams['expires_in'] = $accessToken->getExpiryDateTime()->getTimestamp() - time();
262262
}
263263

264-
$idToken = $this->idTokenBuilder->build(
264+
$idToken = $this->idTokenBuilder->buildFor(
265265
$user,
266266
$accessToken,
267267
$authorizationRequest->getAddClaimsToIdToken(),
@@ -272,7 +272,7 @@ private function completeOidcAuthorizationRequest(AuthorizationRequest $authoriz
272272
$authorizationRequest->getSessionId(),
273273
);
274274

275-
$responseParams['id_token'] = $idToken->toString();
275+
$responseParams['id_token'] = $idToken->getToken();
276276

277277
$response = new RedirectResponse();
278278

src/Server/ResponseTypes/TokenResponse.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ protected function prepareIdTokenExtraParam(AccessTokenEntity $accessToken): arr
126126
throw OidcServerException::accessDenied('No user available for provided user identifier.');
127127
}
128128

129-
$token = $this->idTokenBuilder->build(
129+
//$token = $this->idTokenBuilder->build(
130+
$token = $this->idTokenBuilder->buildFor(
130131
$userEntity,
131132
$accessToken,
132133
false,
@@ -138,7 +139,7 @@ protected function prepareIdTokenExtraParam(AccessTokenEntity $accessToken): arr
138139
);
139140

140141
return [
141-
'id_token' => $token->toString(),
142+
'id_token' => $token->getToken(),
142143
];
143144
}
144145

src/Services/AuthenticationService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ protected function addRelyingPartyAssociation(ClientEntityInterface $oidcClient,
312312
(string)($claims['sub'] ?? $user->getIdentifier()),
313313
$this->getSessionId(),
314314
$oidcClient->getBackChannelLogoutUri(),
315+
$oidcClient->getIdTokenSignedResponseAlg(),
315316
),
316317
);
317318
}

src/Services/Container.php

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,10 +423,15 @@ public function __construct()
423423
$requestRuleManager = new RequestRulesManager($requestRules, $loggerService);
424424
$this->services[RequestRulesManager::class] = $requestRuleManager;
425425

426-
$idTokenBuilder = new IdTokenBuilder($jsonWebTokenBuilderService, $claimTranslatorExtractor);
426+
$idTokenBuilder = new IdTokenBuilder(
427+
$jsonWebTokenBuilderService,
428+
$claimTranslatorExtractor,
429+
$core,
430+
$moduleConfig,
431+
);
427432
$this->services[IdTokenBuilder::class] = $idTokenBuilder;
428433

429-
$logoutTokenBuilder = new LogoutTokenBuilder($jsonWebTokenBuilderService);
434+
$logoutTokenBuilder = new LogoutTokenBuilder($moduleConfig, $loggerService);
430435
$this->services[LogoutTokenBuilder::class] = $logoutTokenBuilder;
431436

432437
$sessionLogoutTicketStoreDb = new LogoutTicketStoreDb($database);

src/Services/IdTokenBuilder.php

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,21 +13,123 @@
1313
use League\OAuth2\Server\Entities\UserEntityInterface;
1414
use RuntimeException;
1515
use SimpleSAML\Module\oidc\Entities\AccessTokenEntity;
16+
use SimpleSAML\Module\oidc\Entities\ClientEntity;
1617
use SimpleSAML\Module\oidc\Entities\Interfaces\ClaimSetInterface;
1718
use SimpleSAML\Module\oidc\Entities\Interfaces\EntityStringRepresentationInterface;
19+
use SimpleSAML\Module\oidc\ModuleConfig;
1820
use SimpleSAML\Module\oidc\Utils\ClaimTranslatorExtractor;
21+
use SimpleSAML\OpenID\Algorithms\SignatureAlgorithmEnum;
22+
use SimpleSAML\OpenID\Codebooks\ClaimsEnum;
23+
use SimpleSAML\OpenID\Core;
24+
use SimpleSAML\OpenID\Core\IdToken;
1925

2026
class IdTokenBuilder
2127
{
2228
public function __construct(
23-
private readonly JsonWebTokenBuilderService $jsonWebTokenBuilderService,
24-
private readonly ClaimTranslatorExtractor $claimExtractor,
29+
protected readonly JsonWebTokenBuilderService $jsonWebTokenBuilderService,
30+
protected readonly ClaimTranslatorExtractor $claimExtractor,
31+
protected readonly Core $core,
32+
protected readonly ModuleConfig $moduleConfig,
2533
) {
2634
}
2735

36+
/**
37+
* @psalm-suppress MixedAssignment
38+
*/
39+
public function buildFor(
40+
UserEntityInterface $userEntity,
41+
AccessTokenEntity $accessToken,
42+
bool $addClaimsFromScopes,
43+
bool $addAccessTokenHash,
44+
?string $nonce,
45+
?int $authTime,
46+
?string $acr,
47+
?string $sessionId,
48+
): IdToken {
49+
if (!is_a($userEntity, ClaimSetInterface::class)) {
50+
throw new RuntimeException('UserEntity must implement ClaimSetInterface');
51+
}
52+
53+
$client = $accessToken->getClient();
54+
if (! $client instanceof ClientEntity) {
55+
throw new RuntimeException('Client is expected to be instance of ' . ClientEntity::class);
56+
}
57+
58+
$protocolSignatureKeyPairBag = $this->moduleConfig->getProtocolSignatureKeyPairBag();
59+
$protocolSignatureKeyPair = $protocolSignatureKeyPairBag->getFirstOrFail();
60+
61+
// ID Token signing algorithm that the client wants.
62+
$clientIdTokenSignedResponseAlg = $client->getIdTokenSignedResponseAlg();
63+
64+
if (is_string($clientIdTokenSignedResponseAlg)) {
65+
$protocolSignatureKeyPair = $protocolSignatureKeyPairBag->getFirstByAlgorithmOrFail(
66+
SignatureAlgorithmEnum::from($clientIdTokenSignedResponseAlg),
67+
);
68+
}
69+
70+
$currentTimestamp = $this->core->helpers()->dateTime()->getUtc()->getTimestamp();
71+
72+
$payload = array_filter([
73+
ClaimsEnum::Iss->value => $this->moduleConfig->getIssuer(),
74+
ClaimsEnum::Iat->value => $currentTimestamp,
75+
ClaimsEnum::Jti->value => $this->core->helpers()->random()->string(),
76+
ClaimsEnum::Aud->value => $client->getIdentifier(),
77+
ClaimsEnum::Nbf->value => $currentTimestamp,
78+
ClaimsEnum::Exp->value => $accessToken->getExpiryDateTime()->getTimestamp(),
79+
ClaimsEnum::Sub->value => $this->core->helpers()->type()->ensureNonEmptyString(
80+
$userEntity->getIdentifier(),
81+
),
82+
ClaimsEnum::Nonce->value => $nonce,
83+
ClaimsEnum::AuthTime->value => $authTime,
84+
ClaimsEnum::ATHash->value => $addAccessTokenHash ?
85+
$this->generateAccessTokenHash(
86+
$accessToken,
87+
$protocolSignatureKeyPair->getSignatureAlgorithm()->value,
88+
) :
89+
null,
90+
ClaimsEnum::Acr->value => $acr,
91+
ClaimsEnum::Sid->value => $sessionId,
92+
]);
93+
94+
// Reduce the number of claims by provided scope.
95+
$claims = $this->claimExtractor->extract(
96+
$accessToken->getScopes(),
97+
$userEntity->getClaims(),
98+
);
99+
$requestedClaims = $accessToken->getRequestedClaims();
100+
$additionalClaims = $this->claimExtractor->extractAdditionalIdTokenClaims(
101+
$requestedClaims,
102+
$userEntity->getClaims(),
103+
);
104+
$claims = array_merge($additionalClaims, $claims);
105+
106+
foreach ($claims as $claimName => $claimValue) {
107+
if (
108+
is_string($claimName) &&
109+
$claimName !== '' &&
110+
($addClaimsFromScopes || array_key_exists($claimName, $additionalClaims))
111+
) {
112+
$payload[$claimName] = $claimValue;
113+
}
114+
}
115+
116+
$header = [
117+
ClaimsEnum::Kid->value => $protocolSignatureKeyPair->getKeyPair()->getKeyId(),
118+
];
119+
120+
return $this->core->idTokenFactory()->fromData(
121+
$protocolSignatureKeyPair->getKeyPair()->getPrivateKey(),
122+
$protocolSignatureKeyPair->getSignatureAlgorithm(),
123+
$payload,
124+
$header,
125+
);
126+
}
127+
28128
/**
29129
* @throws \Exception
30130
* @psalm-suppress ArgumentTypeCoercion
131+
* @deprecated Since v7
132+
* @see self::buildFor()
31133
*/
32134
public function build(
33135
UserEntityInterface $userEntity,

0 commit comments

Comments
 (0)