Skip to content

Commit 89e869a

Browse files
committed
WIP Trust Mark Validation
1 parent 745f0d9 commit 89e869a

File tree

5 files changed

+258
-17
lines changed

5 files changed

+258
-17
lines changed

src/Server/RequestRules/Rules/ClientIdRule.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,7 @@ public function checkRule(
196196
$this->helpers->dateTime()->getFromTimestamp($trustChain->getResolvedExpirationTime()),
197197
$existingClient,
198198
$clientEntityId,
199-
$clientFederationEntity->getJwks(),
199+
$clientFederationEntity->getJwks()->getValue(),
200200
$request,
201201
);
202202

@@ -209,7 +209,7 @@ public function checkRule(
209209
// Check if federation participation is limited by Trust Marks.
210210
if (
211211
$this->moduleConfig->isFederationParticipationLimitedByTrustMarksFor(
212-
$trustChain->getResolvedTrustAnchor()->getIssuer(),
212+
$trustAnchorEntityConfiguration->getIssuer(),
213213
)
214214
) {
215215
$this->federationParticipationValidator->byTrustMarksFor($trustChain);

src/Server/Validators/BearerTokenValidator.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ protected function initJwtConfiguration(): void
7676
InMemory::plainText('empty', 'empty'),
7777
);
7878

79-
/** @psalm-suppress ArgumentTypeCoercion */
79+
/** @psalm-suppress DeprecatedMethod, ArgumentTypeCoercion */
8080
$this->jwtConfiguration->setValidationConstraints(
8181
new StrictValidAt(new SystemClock(new DateTimeZone(date_default_timezone_get()))),
8282
new SignedWith(

src/Services/Container.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,7 @@ public function __construct()
349349
$this->services[JwksResolver::class] = $jwksResolver;
350350
$federationParticipationValidator = new FederationParticipationValidator(
351351
$moduleConfig,
352+
$federation,
352353
$loggerService,
353354
);
354355
$this->services[FederationParticipationValidator::class] = $federationParticipationValidator;

src/Utils/FederationParticipationValidator.php

Lines changed: 253 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,20 @@
44

55
namespace SimpleSAML\Module\oidc\Utils;
66

7+
use SimpleSAML\Module\oidc\Codebooks\LimitsEnum;
78
use SimpleSAML\Module\oidc\ModuleConfig;
89
use SimpleSAML\Module\oidc\Services\LoggerService;
910
use SimpleSAML\OpenID\Exceptions\TrustMarkException;
11+
use SimpleSAML\OpenID\Federation;
12+
use SimpleSAML\OpenID\Federation\Claims\TrustMarksClaimBag;
13+
use SimpleSAML\OpenID\Federation\EntityStatement;
1014
use SimpleSAML\OpenID\Federation\TrustChain;
1115

1216
class FederationParticipationValidator
1317
{
1418
public function __construct(
1519
protected readonly ModuleConfig $moduleConfig,
20+
protected readonly Federation $federation,
1621
protected readonly LoggerService $loggerService,
1722
) {
1823
}
@@ -26,34 +31,271 @@ public function __construct(
2631
*/
2732
public function byTrustMarksFor(TrustChain $trustChain): void
2833
{
29-
$trustAnchor = $trustChain->getResolvedTrustAnchor();
34+
$leafEntityConfiguration = $trustChain->getResolvedLeaf();
35+
$trustAnchorEntityConfiguration = $trustChain->getResolvedTrustAnchor();
36+
37+
$this->loggerService->debug(
38+
sprintf(
39+
'Validating federation participation by Trust Marks for leaf %s and Trust Anchor %s.',
40+
$leafEntityConfiguration->getIssuer(),
41+
$trustAnchorEntityConfiguration->getIssuer(),
42+
),
43+
);
3044

3145
$trustMarkLimitsRules = $this->moduleConfig->getTrustMarksNeededForFederationParticipationFor(
32-
$trustAnchor->getIssuer(),
46+
$trustAnchorEntityConfiguration->getIssuer(),
3347
);
3448

3549
if (empty($trustMarkLimitsRules)) {
36-
$this->loggerService->debug('No Trust Mark limits imposed for ' . $trustAnchor->getIssuer());
50+
$this->loggerService->debug(
51+
'No Trust Mark limits imposed for ' . $trustAnchorEntityConfiguration->getIssuer(),
52+
);
3753
return;
3854
}
3955

40-
$this->loggerService->debug('Trust Mark limits for ' . $trustAnchor->getIssuer(), $trustMarkLimitsRules);
56+
$this->loggerService->debug(
57+
'Trust Mark limits for ' . $trustAnchorEntityConfiguration->getIssuer(),
58+
$trustMarkLimitsRules,
59+
);
60+
61+
62+
/**
63+
* @var string $limitId
64+
* @var non-empty-string[] $limitedTrustMarkIds
65+
*/
66+
foreach ($trustMarkLimitsRules as $limitId => $limitedTrustMarkIds) {
67+
$limit = LimitsEnum::from($limitId);
4168

42-
$leaf = $trustChain->getResolvedLeaf();
43-
$leafTrustMarks = $leaf->getTrustMarks();
69+
if ($limit === LimitsEnum::OneOf) {
70+
$this->validateTrustMarksClaimBagAsOneOfLimit(
71+
$limitedTrustMarkIds,
72+
$leafEntityConfiguration,
73+
$trustAnchorEntityConfiguration,
74+
);
75+
} else {
76+
$this->validateTrustMarksClaimBagAsAllOfLimit(
77+
$limitedTrustMarkIds,
78+
$leafEntityConfiguration,
79+
$trustAnchorEntityConfiguration,
80+
);
81+
}
82+
}
83+
}
84+
85+
/**
86+
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
87+
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
88+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
89+
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
90+
*/
91+
protected function ensureTrustMarksClaimBag(EntityStatement $leafEntityConfiguration): TrustMarksClaimBag
92+
{
93+
$leafTrustMarksClaimBag = $leafEntityConfiguration->getTrustMarks();
4494

45-
if (is_null($leafTrustMarks)) {
95+
if (is_null($leafTrustMarksClaimBag)) {
4696
$error = sprintf(
4797
'Leaf entity %s does not have any Trust Marks available.',
48-
$leaf->getIssuer(),
98+
$leafEntityConfiguration->getIssuer(),
4999
);
50100

51-
$this->loggerService->error($error, compact('trustMarkLimitsRules'));
101+
$this->loggerService->error($error);
52102
throw new TrustMarkException($error);
53103
}
54104

55-
// Leaf has some Trust Marks.
105+
$this->loggerService->debug(
106+
'Leaf has Trust Marks available.',
107+
['leafTrustMarksClaimBag' => $leafTrustMarksClaimBag->jsonSerialize()],
108+
);
109+
110+
return $leafTrustMarksClaimBag;
111+
}
112+
113+
/**
114+
* @param non-empty-string[] $limitedTrustMarkIds
115+
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
116+
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
117+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
118+
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
119+
*/
120+
public function validateTrustMarksClaimBagAsOneOfLimit(
121+
array $limitedTrustMarkIds,
122+
EntityStatement $leafEntityConfiguration,
123+
EntityStatement $trustAnchorEntityConfiguration,
124+
): void {
125+
if (empty($limitedTrustMarkIds)) {
126+
$this->loggerService->debug('No Trust Mark limits given for OneOf limit rule, nothing to do.');
127+
return;
128+
}
129+
130+
$this->loggerService->debug(
131+
sprintf(
132+
'Validating that entity %s has at least one valid Trust Mark for Trust Anchor %s.',
133+
$leafEntityConfiguration->getIssuer(),
134+
$trustAnchorEntityConfiguration->getIssuer(),
135+
),
136+
['limitedTrustMarkIds' => $limitedTrustMarkIds],
137+
);
138+
139+
$trustMarksClaimBag = $this->ensureTrustMarksClaimBag($leafEntityConfiguration);
140+
141+
foreach ($limitedTrustMarkIds as $limitedTrustMarkId) {
142+
$trustMarksClaimValues = $trustMarksClaimBag->gerAllFor($limitedTrustMarkId);
143+
if (empty($trustMarksClaimValues)) {
144+
$this->loggerService->debug(
145+
sprintf(
146+
'There are no claims for Trust Mark ID %s. Trying next if available.',
147+
$limitedTrustMarkId,
148+
),
149+
);
150+
continue;
151+
}
152+
153+
$this->loggerService->debug(
154+
sprintf(
155+
'There is/are %s claim/claims for Trust Mark ID %s.',
156+
count($trustMarksClaimValues),
157+
$limitedTrustMarkId,
158+
),
159+
);
160+
161+
foreach ($trustMarksClaimValues as $idx => $trustMarksClaimValue) {
162+
$this->loggerService->debug(
163+
sprintf(
164+
'Validating claim %s for Trust Mark ID %s',
165+
$idx,
166+
$limitedTrustMarkId,
167+
),
168+
['trustMarkClaim' => $trustMarksClaimValue->jsonSerialize()],
169+
);
170+
171+
try {
172+
$this->federation->trustMarkValidator()->forTrustMarksClaimValue(
173+
$trustMarksClaimValue,
174+
$leafEntityConfiguration,
175+
$trustAnchorEntityConfiguration,
176+
);
177+
178+
$this->loggerService->debug(
179+
sprintf(
180+
'Trust Mark ID %s validated using OneOf limit rule for entity %s for Trust Anchor %s.',
181+
$limitedTrustMarkId,
182+
$leafEntityConfiguration->getIssuer(),
183+
$trustAnchorEntityConfiguration->getIssuer(),
184+
),
185+
);
186+
return;
187+
} catch (\Throwable $exception) {
188+
$this->loggerService->debug(
189+
sprintf(
190+
'Trust Mark ID %s validation failed with error: %s. Trying next if available.',
191+
$limitedTrustMarkId,
192+
$exception->getMessage(),
193+
),
194+
);
195+
continue;
196+
}
197+
}
198+
}
199+
200+
$error = sprintf(
201+
'Leaf entity %s does not have any valid Trust Marks from the given list (%s).',
202+
$leafEntityConfiguration->getIssuer(),
203+
implode(',', $limitedTrustMarkIds),
204+
);
205+
206+
$this->loggerService->error($error);
207+
throw new TrustMarkException($error);
208+
}
209+
210+
/**
211+
* @param non-empty-string[] $limitedTrustMarkIds
212+
* @throws \SimpleSAML\OpenID\Exceptions\InvalidValueException
213+
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
214+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
215+
* @throws \SimpleSAML\OpenID\Exceptions\TrustMarkException
216+
*/
217+
public function validateTrustMarksClaimBagAsAllOfLimit(
218+
array $limitedTrustMarkIds,
219+
EntityStatement $leafEntityConfiguration,
220+
EntityStatement $trustAnchorEntityConfiguration,
221+
): void {
222+
if (empty($limitedTrustMarkIds)) {
223+
$this->loggerService->debug('No Trust Mark limits given for AllOf limit rule, nothing to do.');
224+
return;
225+
}
226+
227+
$this->loggerService->debug(
228+
sprintf(
229+
'Validating that entity %s has all valid Trust Marks for Trust Anchor %s.',
230+
$leafEntityConfiguration->getIssuer(),
231+
$trustAnchorEntityConfiguration->getIssuer(),
232+
),
233+
['limitedTrustMarkIds' => $limitedTrustMarkIds],
234+
);
235+
236+
$trustMarksClaimBag = $this->ensureTrustMarksClaimBag($leafEntityConfiguration);
237+
238+
foreach ($limitedTrustMarkIds as $limitedTrustMarkId) {
239+
$trustMarksClaimValues = $trustMarksClaimBag->gerAllFor($limitedTrustMarkId);
240+
241+
if (empty($trustMarksClaimValues)) {
242+
$error = sprintf(
243+
'There are no claims for Trust Mark ID %s.',
244+
$limitedTrustMarkId,
245+
);
246+
$this->loggerService->error($error);
247+
throw new TrustMarkException($error);
248+
}
56249

57-
// TODO mivanci continue
250+
$this->loggerService->debug(
251+
sprintf(
252+
'There is/are %s claim/claims for Trust Mark ID %s.',
253+
count($trustMarksClaimValues),
254+
$limitedTrustMarkId,
255+
),
256+
);
257+
258+
foreach ($trustMarksClaimValues as $idx => $trustMarksClaimValue) {
259+
$this->loggerService->debug(
260+
sprintf(
261+
'Validating claim %s for Trust Mark ID %s',
262+
$idx,
263+
$limitedTrustMarkId,
264+
),
265+
['trustMarkClaim' => $trustMarksClaimValue->jsonSerialize()],
266+
);
267+
268+
try {
269+
$this->federation->trustMarkValidator()->forTrustMarksClaimValue(
270+
$trustMarksClaimValue,
271+
$leafEntityConfiguration,
272+
$trustAnchorEntityConfiguration,
273+
);
274+
275+
$this->loggerService->debug(
276+
sprintf(
277+
'Trust Mark ID %s validated. Trying next if available.',
278+
$limitedTrustMarkId,
279+
),
280+
);
281+
} catch (\Throwable $exception) {
282+
$error = sprintf(
283+
'Trust Mark ID %s validation failed with error: %s.',
284+
$limitedTrustMarkId,
285+
$exception->getMessage(),
286+
);
287+
$this->loggerService->error($error);
288+
throw new TrustMarkException($error);
289+
}
290+
}
291+
}
292+
293+
$this->loggerService->debug(
294+
sprintf(
295+
'Entity %s has all valid Trust Marks for Trust Anchor %s.',
296+
$leafEntityConfiguration->getIssuer(),
297+
$trustAnchorEntityConfiguration->getIssuer(),
298+
),
299+
);
58300
}
59301
}

tests/unit/src/Services/AuthenticationServiceTest.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,7 @@ public function testGetAuthenticateUserItThrowsIfClaimsNotExist(): void
326326
unset($invalidState['Attributes'][self::USER_ID_ATTR]);
327327

328328
$this->expectException(Exception::class);
329-
$this->expectExceptionMessageMatches(
330-
"/Attribute `useridattr` doesn\'t exists in claims. Available attributes are:/",
331-
);
329+
$this->expectExceptionMessageMatches("/User identifier attribute /");
332330

333331
$this->mock()->getAuthenticateUser($invalidState);
334332
}

0 commit comments

Comments
 (0)