Skip to content

Commit a7ea7b3

Browse files
committed
Introduce trust_anchor_hints claim
1 parent b72da92 commit a7ea7b3

3 files changed

Lines changed: 94 additions & 0 deletions

File tree

src/Codebooks/ClaimsEnum.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ enum ClaimsEnum: string
182182
// Type
183183
case Typ = 'typ';
184184
case Type = 'type';
185+
case TrustAnchorHints = 'trust_anchor_hints';
185186
case TrustChain = 'trust_chain';
186187
case TrustMark = 'trust_mark';
187188
case TrustMarkIssuers = 'trust_mark_issuers';

src/Federation/EntityStatement.php

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,42 @@ public function getAuthorityHints(): ?array
127127
}
128128

129129

130+
/**
131+
* @throws \SimpleSAML\OpenID\Exceptions\EntityStatementException
132+
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
133+
* @return null|non-empty-string[]
134+
*/
135+
public function getTrustAnchorHints(): ?array
136+
{
137+
// trust_anchor_hints
138+
// OPTIONAL. An array of strings representing the Entity Identifiers of Trust Anchors trusted by the Entity.
139+
// Its value MUST NOT be the empty array []. This Claim MUST NOT be present in Entity Configurations
140+
// of Trust Anchors with no Superiors.
141+
142+
$claimKey = ClaimsEnum::TrustAnchorHints->value;
143+
$trustAnchorHints = $this->getPayloadClaim($claimKey);
144+
145+
if (is_null($trustAnchorHints)) {
146+
return null;
147+
}
148+
149+
if (!is_array($trustAnchorHints)) {
150+
throw new EntityStatementException('Invalid Trust Anchor Hints claim.');
151+
}
152+
153+
if ($trustAnchorHints === []) {
154+
throw new EntityStatementException('Empty Trust Anchor Hints claim encountered.');
155+
}
156+
157+
// It MUST NOT be present in Subordinate Statements.
158+
if (!$this->isConfiguration()) {
159+
throw new EntityStatementException('Trust Anchor Hints claim encountered in non-configuration statement.');
160+
}
161+
162+
return $this->helpers->type()->ensureArrayWithValuesAsNonEmptyStrings($trustAnchorHints, $claimKey);
163+
}
164+
165+
130166
/**
131167
* @return ?array<string,mixed>
132168
* @throws \SimpleSAML\OpenID\Exceptions\JwsException
@@ -379,6 +415,7 @@ protected function validate(): void
379415
$this->getType(...),
380416
$this->getKeyId(...),
381417
$this->getAuthorityHints(...),
418+
$this->getTrustAnchorHints(...),
382419
$this->getMetadata(...),
383420
$this->getMetadataPolicy(...),
384421
$this->getTrustMarks(...),

tests/src/Federation/EntityStatementTest.php

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ protected function setUp(): void
136136
$typeHelperMock->method('ensureString')->willReturnArgument(0);
137137
$typeHelperMock->method('ensureNonEmptyString')->willReturnArgument(0);
138138
$typeHelperMock->method('ensureInt')->willReturnArgument(0);
139+
$typeHelperMock->method('ensureArrayWithValuesAsNonEmptyStrings')->willReturnArgument(0);
139140

140141
$this->claimFactoryMock = $this->createMock(ClaimFactory::class);
141142
$this->federationClaimFactoryMock = $this->createMock(FederationClaimFactory::class);
@@ -277,6 +278,61 @@ public function testThrowsIfAuthorityHintsNotInConfigurationStatement(): void
277278
}
278279

279280

281+
public function testCanDefineTrustAnchorHints(): void
282+
{
283+
$this->validPayload['trust_anchor_hints'] = ['trust-anchor-id'];
284+
285+
$this->signatureMock->method('getProtectedHeader')->willReturn($this->sampleHeader);
286+
$this->jsonHelperMock->method('decode')->willReturn($this->validPayload);
287+
288+
289+
$this->assertSame(['trust-anchor-id'], $this->sut()->getTrustAnchorHints());
290+
}
291+
292+
293+
public function testThrowsOnInvalidTrustAnchorHints(): void
294+
{
295+
$this->validPayload['trust_anchor_hints'] = 'invalid';
296+
297+
$this->expectException(JwsException::class);
298+
$this->expectExceptionMessage('Invalid');
299+
300+
$this->signatureMock->method('getProtectedHeader')->willReturn($this->sampleHeader);
301+
$this->jsonHelperMock->method('decode')->willReturn($this->validPayload);
302+
303+
$this->sut();
304+
}
305+
306+
307+
public function testThrowsOnEmptyTrustAnchorHints(): void
308+
{
309+
$this->validPayload['trust_anchor_hints'] = [];
310+
311+
$this->expectException(JwsException::class);
312+
$this->expectExceptionMessage('Empty');
313+
314+
$this->signatureMock->method('getProtectedHeader')->willReturn($this->sampleHeader);
315+
$this->jsonHelperMock->method('decode')->willReturn($this->validPayload);
316+
317+
$this->sut();
318+
}
319+
320+
321+
public function testThrowsIfTrustAnchorHintsNotInConfigurationStatement(): void
322+
{
323+
$this->validPayload['trust_anchor_hints'] = ['trust-anchor-id'];
324+
$this->validPayload['iss'] = 'something-else';
325+
326+
$this->expectException(JwsException::class);
327+
$this->expectExceptionMessage('non-configuration');
328+
329+
$this->signatureMock->method('getProtectedHeader')->willReturn($this->sampleHeader);
330+
$this->jsonHelperMock->method('decode')->willReturn($this->validPayload);
331+
332+
$this->sut();
333+
}
334+
335+
280336
public function testTrustMarksAreOptional(): void
281337
{
282338
$payload = $this->validPayload;

0 commit comments

Comments
 (0)