Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
forwarded to the underlying Guzzle HTTP client used by league/oauth2-client.
Closes the long-standing inability to bound HTTP requests to the IdP.

### Fixed

- Preserve original cause via `$previous` in `CliLoginHelper` and
`OpenIdLoginAuthenticator::validateClaims` exception wraps. Previously
the message was copied but the chain to the originating PSR cache or
upstream OIDC failure was lost, making logs harder to debug.

### Changed

- Mapped LoginController failures to 404 (unknown provider) or 503
Expand Down
2 changes: 1 addition & 1 deletion src/Security/OpenIdLoginAuthenticator.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected function validateClaims(Request $request): array
// Authentication successful
} catch (ItkOpenIdConnectException $exception) {
// Handle failed authentication
throw new ValidationException($exception->getMessage());
throw new ValidationException($exception->getMessage(), previous: $exception);
}

/** @var array<string, string> $claimsArray */
Expand Down
8 changes: 4 additions & 4 deletions src/Util/CliLoginHelper.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function createToken(string $username): string
try {
$revCacheItem = $this->cache->getItem($encodedUsername);
} catch (InvalidArgumentException $e) {
throw new CacheException($e->getMessage());
throw new CacheException($e->getMessage(), previous: $e);
}

if ($revCacheItem->isHit()) {
Expand All @@ -53,7 +53,7 @@ public function createToken(string $username): string
try {
$cacheItem = $this->cache->getItem($token);
} catch (InvalidArgumentException $e) {
throw new CacheException($e->getMessage());
throw new CacheException($e->getMessage(), previous: $e);
}

$cacheItem->set($encodedUsername);
Expand All @@ -73,7 +73,7 @@ public function getUsername(string $token): ?string
try {
$usernameItem = $this->cache->getItem($token);
} catch (InvalidArgumentException $e) {
throw new CacheException($e->getMessage());
throw new CacheException($e->getMessage(), previous: $e);
}

if (!$usernameItem->isHit()) {
Expand All @@ -91,7 +91,7 @@ public function getUsername(string $token): ?string
$this->cache->deleteItem($token);
$this->cache->deleteItem($username);
} catch (InvalidArgumentException $e) {
throw new CacheException($e->getMessage());
throw new CacheException($e->getMessage(), previous: $e);
}

return $this->decodeKey($username);
Expand Down
15 changes: 11 additions & 4 deletions tests/Security/OpenIdLoginAuthenticatorTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -87,18 +87,25 @@ public function testValidateClaimsMissingCode(): void

public function testValidateClaimsCodeDoesNotValidate(): void
{
$cause = new ClaimsException('test message');
$stubProvider = $this->createStub(OpenIdConfigurationProvider::class);
$stubProvider->method('validateIdToken')->willThrowException(new ClaimsException('test message'));
$stubProvider->method('validateIdToken')->willThrowException($cause);
$this->stubProviderManager->method('getProvider')->willReturn($stubProvider);

$request = $this->createStub(Request::class);
$request->query = new InputBag(['state' => 'test_state', 'code' => 'test_code']);

$this->setupStubSessionOnRequest($request);

$this->expectException(ValidationException::class);
$this->expectExceptionMessage('test message');
$this->authenticator->authenticate($request);
try {
$this->authenticator->authenticate($request);
} catch (ValidationException $thrown) {
$this->assertSame('test message', $thrown->getMessage());
$this->assertSame($cause, $thrown->getPrevious(), 'Original cause must be chained');

return;
}
$this->fail('Expected ValidationException');
}

public function testValidateClaimsSuccess(): void
Expand Down
61 changes: 41 additions & 20 deletions tests/Util/CliLoginHelperTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,16 +83,21 @@ public function testCreateTokenAndGetUsername(): void

public function testCreateTokenThrowsCacheExceptionOnGetItem(): void
{
$cause = new TestInvalidArgumentException('Cache error');
$stubCache = $this->createStub(CacheItemPoolInterface::class);
$stubCache->method('getItem')
->willThrowException(new TestInvalidArgumentException('Cache error'));
$stubCache->method('getItem')->willThrowException($cause);

$cliHelper = new CliLoginHelper($stubCache);

$this->expectException(CacheException::class);
$this->expectExceptionMessage('Cache error');
try {
$cliHelper->createToken('test_user');
} catch (CacheException $thrown) {
$this->assertSame('Cache error', $thrown->getMessage());
$this->assertSame($cause, $thrown->getPrevious(), 'Original cause must be chained');

$cliHelper->createToken('test_user');
return;
}
$this->fail('Expected CacheException');
}

public function testCreateTokenThrowsCacheExceptionOnSecondGetItem(): void
Expand All @@ -101,38 +106,49 @@ public function testCreateTokenThrowsCacheExceptionOnSecondGetItem(): void
$stubCacheItem->method('isHit')->willReturn(false);
$stubCacheItem->method('get')->willReturn(null);

$cause = new TestInvalidArgumentException('Second cache error');
$stubCache = $this->createStub(CacheItemPoolInterface::class);
$callCount = 0;
$stubCache->method('getItem')
->willReturnCallback(function () use ($stubCacheItem, &$callCount) {
->willReturnCallback(function () use ($stubCacheItem, $cause, &$callCount) {
++$callCount;
if (1 === $callCount) {
return $stubCacheItem;
}
throw new TestInvalidArgumentException('Second cache error');
throw $cause;
});
$stubCache->method('save')->willReturn(true);

$cliHelper = new CliLoginHelper($stubCache);

$this->expectException(CacheException::class);
$this->expectExceptionMessage('Second cache error');
try {
$cliHelper->createToken('another_user');
} catch (CacheException $thrown) {
$this->assertSame('Second cache error', $thrown->getMessage());
$this->assertSame($cause, $thrown->getPrevious(), 'Original cause must be chained');

$cliHelper->createToken('another_user');
return;
}
$this->fail('Expected CacheException');
}

public function testGetUsernameThrowsCacheExceptionOnGetItem(): void
{
$cause = new TestInvalidArgumentException('Cache error');
$stubCache = $this->createStub(CacheItemPoolInterface::class);
$stubCache->method('getItem')
->willThrowException(new TestInvalidArgumentException('Cache error'));
$stubCache->method('getItem')->willThrowException($cause);

$cliHelper = new CliLoginHelper($stubCache);

$this->expectException(CacheException::class);
$this->expectExceptionMessage('Cache error');
try {
$cliHelper->getUsername('some-token');
} catch (CacheException $thrown) {
$this->assertSame('Cache error', $thrown->getMessage());
$this->assertSame($cause, $thrown->getPrevious(), 'Original cause must be chained');

$cliHelper->getUsername('some-token');
return;
}
$this->fail('Expected CacheException');
}

public function testCreateTokenThrowsCacheExceptionOnNonStringCachedToken(): void
Expand Down Expand Up @@ -175,16 +191,21 @@ public function testGetUsernameThrowsCacheExceptionOnDeleteItem(): void
$stubCacheItem->method('isHit')->willReturn(true);
$stubCacheItem->method('get')->willReturn('encoded_username');

$cause = new TestInvalidArgumentException('Delete error');
$stubCache = $this->createStub(CacheItemPoolInterface::class);
$stubCache->method('getItem')->willReturn($stubCacheItem);
$stubCache->method('deleteItem')
->willThrowException(new TestInvalidArgumentException('Delete error'));
$stubCache->method('deleteItem')->willThrowException($cause);

$cliHelper = new CliLoginHelper($stubCache);

$this->expectException(CacheException::class);
$this->expectExceptionMessage('Delete error');
try {
$cliHelper->getUsername('some-token');
} catch (CacheException $thrown) {
$this->assertSame('Delete error', $thrown->getMessage());
$this->assertSame($cause, $thrown->getPrevious(), 'Original cause must be chained');

$cliHelper->getUsername('some-token');
return;
}
$this->fail('Expected CacheException');
}
}
Loading