diff --git a/.gitignore b/.gitignore index 3d245f0..2e7cdfb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ vendor-bin/**/composer.lock /build/ /tests/output/ .phpunit.result.cache +tests/unit/PopTest.php # SonarCloud scanner .scannerwork diff --git a/lib/Client.php b/lib/Client.php index 6095fa1..a9148ec 100644 --- a/lib/Client.php +++ b/lib/Client.php @@ -53,6 +53,8 @@ class Client extends OpenIDConnectClient { * @param IURLGenerator $generator * @param ISession $session * @param ILogger $logger + * + * @throws \JsonException */ public function __construct( IConfig $config, @@ -147,11 +149,12 @@ public function getAutoUpdateConfig(): array { /** * @throws OpenIDConnectClientException + * @throws \JsonException */ public function getWellKnownConfig() { if (!$this->wellKnownConfig) { $well_known_config_url = \rtrim($this->getProviderURL(), '/') . '/.well-known/openid-configuration'; - $this->wellKnownConfig = \json_decode($this->fetchURL($well_known_config_url), false); + $this->wellKnownConfig = \json_decode($this->fetchURL($well_known_config_url), false, 512, JSON_THROW_ON_ERROR); } return $this->wellKnownConfig; } @@ -160,12 +163,64 @@ public function mode() { return $this->getOpenIdConfig()['mode'] ?? 'userid'; } + /** + * @return object|null + */ + public function getAccessTokenPayload(): ?object { + return parent::getAccessTokenPayload(); + } + + /** + * @throws OpenIDConnectClientException + * @throws \JsonException + */ + public function verifyToken(string $token) { + $config = $this->getOpenIdConfig(); + $this->setAccessToken($token); + $payload = $this->getAccessTokenPayload(); + if ($payload) { + if (!$this->verifyJWTsignature($token)) { + $this->logger->error('Token cannot be verified: ' . $token); + throw new OpenIDConnectClientException('Token cannot be verified.'); + } + $this->logger->debug('Access token payload: ' . \json_encode($payload, JSON_THROW_ON_ERROR)); + /* @phan-suppress-next-line PhanTypeExpectedObjectPropAccess */ + return $payload->exp; + } + + # use token introspection to verify the token + $introspectionClientId = $config['token-introspection-endpoint-client-id'] ?? null; + $introspectionClientSecret = $config['token-introspection-endpoint-client-secret'] ?? null; + $tokenExchangeMode = $config['exchange-token-mode-before-introspection'] ?? null; + + if ($tokenExchangeMode) { + $token = $tokenExchangeMode === 'refresh-token' ? $this->session->get('oca.openid-connect.refresh-token') : $this->session->get('oca.openid-connect.access-token'); + $this->logger->debug("Starting token-exchange to verify session with subject_token mode: $tokenExchangeMode"); + + $token = $this->exchangeToken($token, $tokenExchangeMode); + } + + $introData = $this->introspectToken($token, '', $introspectionClientId, $introspectionClientSecret); + $this->logger->debug('Introspection info: ' . \json_encode($introData, JSON_THROW_ON_ERROR)); + if (\property_exists($introData, 'error')) { + $this->logger->error('Token introspection failed: ' . \json_encode($introData, JSON_THROW_ON_ERROR)); + throw new OpenIDConnectClientException("Verifying token failed: {$introData->error}"); + } + if (!$introData->active) { + $this->logger->error('Token (as per introspection) is inactive: ' . \json_encode($introData, JSON_THROW_ON_ERROR)); + throw new OpenIDConnectClientException('Token (as per introspection) is inactive'); + } + return $introData->exp; + } + + /** + * @throws OpenIDConnectClientException + * @throws \JsonException + */ public function getUserInfo() { $openIdConfig = $this->getOpenIdConfig(); - if (isset($openIdConfig['use-access-token-payload-for-user-info']) && $openIdConfig['use-access-token-payload-for-user-info']) { - if ($payload = $this->getAccessTokenPayload()) { - return $payload; - } + if ($payload = $this->getAccessTokenPayload()) { + return $payload; } if (isset($openIdConfig['use-access-token-introspection-for-user-info']) && $openIdConfig['use-access-token-introspection-for-user-info']) { @@ -312,6 +367,7 @@ public function getCodeChallengeMethod() { * * @return bool * @throws OpenIDConnectClientException + * @throws \JsonException */ public function authenticate() : bool { $redirectUrl = $this->generator->linkToRouteAbsolute('openidconnect.loginFlow.login'); diff --git a/lib/OpenIdConnectAuthModule.php b/lib/OpenIdConnectAuthModule.php index f13dab8..941911b 100644 --- a/lib/OpenIdConnectAuthModule.php +++ b/lib/OpenIdConnectAuthModule.php @@ -112,8 +112,8 @@ public function authToken(string $type, string $token): ?IUser { if ($this->client->getOpenIdConfig() === null) { return null; } - // 1. verify JWT signature - $expiry = $this->verifyJWT($type, $token); + // 1. verify token + $expiry = $this->verifyToken($token); // 2. verify expiry if ($expiry) { @@ -150,40 +150,14 @@ public function getUserPassword(IRequest $request): string { /** * @throws OpenIDConnectClientException */ - private function verifyJWT(string $type, string $token) { + private function verifyToken(string $token) { $cache = $this->getCache(); $userInfo = $cache->get($token); if ($userInfo) { return $userInfo['exp']; } - $config = $this->client->getOpenIdConfig(); - $payload = $this->client->getAccessTokenPayload(); - - if (!$payload) { - $introspectionClientId = $config['token-introspection-endpoint-client-id'] ?? null; - $introspectionClientSecret = $config['token-introspection-endpoint-client-secret'] ?? null; - - $introData = $this->client->introspectToken($token, '', $introspectionClientId, $introspectionClientSecret); - $this->logger->debug('Introspection info: ' . \json_encode($introData)); - if (\property_exists($introData, 'error')) { - $this->logger->error('Token introspection failed: ' . \json_encode($introData)); - throw new OpenIDConnectClientException("Verifying token failed: {$introData->error}"); - } - if (!$introData->active) { - $this->logger->error('Token (as per introspection) is inactive: ' . \json_encode($introData)); - throw new OpenIDConnectClientException('Token (as per introspection) is inactive'); - } - return $introData->exp; - } - if (!$this->client->verifyJWTsignature($token)) { - $this->logger->error('Token cannot be verified: ' . $token); - throw new OpenIDConnectClientException('Token cannot be verified.'); - } - $this->client->setAccessToken($token); - $payload = $this->client->getAccessTokenPayload(); - $this->logger->debug('Access token payload: ' . \json_encode($payload)); - /* @phan-suppress-next-line PhanTypeExpectedObjectPropAccess */ - return $payload->exp; + + return $this->client->verifyToken($token); } /** diff --git a/lib/SessionVerifier.php b/lib/SessionVerifier.php index a34adef..297d3f2 100644 --- a/lib/SessionVerifier.php +++ b/lib/SessionVerifier.php @@ -62,6 +62,7 @@ public function __construct( /** * @throws HintException * @throws OpenIDConnectClientException + * @throws \JsonException */ public function verifySession(): void { // verify open id token/session @@ -97,65 +98,26 @@ public function verifySession(): void { return; } - $client->setAccessToken($accessToken); - $openIdConfig = $client->getOpenIdConfig(); - - # if the access token is a JWT we use it for verification - $payload = $this->client->getAccessTokenPayload(); - if (!$payload) { - $introspectionClientId = $openIdConfig['token-introspection-endpoint-client-id'] ?? null; - $introspectionClientSecret = $openIdConfig['token-introspection-endpoint-client-secret'] ?? null; - - if (isset($openIdConfig['exchange-token-mode-before-introspection'])) { - $mode = $openIdConfig['exchange-token-mode-before-introspection']; - $token = $mode === 'refresh-token' ? $this->session->get('oca.openid-connect.refresh-token') : $this->session->get('oca.openid-connect.access-token'); - $this->logger->debug("Starting token-exchange to verify session with subject_token mode: $mode"); - - try { - $accessToken = $this->client->exchangeToken($token, $mode); - } catch (\Exception $e) { - $this->logger->debug("Token Exchange failed, logout: " . $e->getMessage()); - $this->logout(); - return; - } - } - - $introData = $client->introspectToken($accessToken, '', $introspectionClientId, $introspectionClientSecret); - $this->logger->debug('Introspection info: ' . \json_encode($introData) . ' for access token:' . $accessToken); - if (\property_exists($introData, 'error')) { - $this->logger->error('Token introspection failed: ' . \json_encode($introData)); - $this->logout(); - throw new HintException("Verifying token failed: {$introData->error}"); - } - if (!$introData->active) { - $this->logger->error('Token (as per introspection) is inactive: ' . \json_encode($introData)); - $this->logout(); - return; - } - - $this->getCache()->set($accessToken, $introData->exp); - $this->refreshToken($introData->exp); - } else { - if (!$client->verifyJWTsignature($accessToken)) { - $this->logger->error('Token cannot be verified: ' . $accessToken); - $this->logout(); - throw new OpenIDConnectClientException('Token cannot be verified.'); - } - $payload = $client->getAccessTokenPayload(); - $this->logger->debug('Access token payload: ' . \json_encode($payload) . ' for access token:' . $accessToken); - - $this->getCache()->set($accessToken, $payload->exp); + try { + $expiry = $client->verifyToken($accessToken); + $this->getCache()->set($accessToken, $expiry); /* @phan-suppress-next-line PhanTypeExpectedObjectPropAccess */ - $this->refreshToken($payload->exp); + $this->refreshToken($expiry); + } catch (\Exception $ex) { + $this->logout(); + throw $ex; } } + /** + * @throws \JsonException + */ public function afterLogout($accessToken, $idToken): void { // only call if access token is still valid try { $this->logger->debug('OIDC Logout: revoking token' . $accessToken); $revokeData = $this->client->revokeToken($accessToken); - $this->logger->debug('Revocation info: ' . \json_encode($revokeData)); + $this->logger->debug('Revocation info: ' . \json_encode($revokeData, JSON_THROW_ON_ERROR)); } catch (OpenIDConnectClientException $ex) { $this->logger->logException($ex); } @@ -180,6 +142,7 @@ private function getCache(): ICache { * @param int $exp * @throws OpenIDConnectClientException * @throws HintException + * @throws \JsonException */ private function refreshToken($exp): void { $expiring = $exp - \time(); @@ -187,11 +150,11 @@ private function refreshToken($exp): void { $refreshToken = $this->session->get('oca.openid-connect.refresh-token'); if ($refreshToken) { $response = $this->client->refreshToken($refreshToken); - $this->logger->debug('RefreshToken response: ' . \json_encode($response)); + $this->logger->debug('RefreshToken response: ' . \json_encode($response, JSON_THROW_ON_ERROR)); if (\property_exists($response, 'error')) { - $this->logger->error('Refresh token failed: ' . \json_encode($response)); + $this->logger->error('Refresh token failed: ' . \json_encode($response, JSON_THROW_ON_ERROR)); $this->logout(); - throw new HintException("Verifying token failed: {$response->error}"); + throw new HintException("Verifying token failed: $response->error"); } $this->session->set('oca.openid-connect.id-token', $this->client->getIdToken()); diff --git a/tests/unit/ClientTest.php b/tests/unit/ClientTest.php index cc064df..2799d54 100644 --- a/tests/unit/ClientTest.php +++ b/tests/unit/ClientTest.php @@ -22,6 +22,8 @@ namespace OCA\OpenIdConnect\Tests\Unit; +use JsonException; +use Jumbojett\OpenIDConnectClientException; use OCA\OpenIdConnect\Client; use OCP\IConfig; use OCP\ISession; @@ -76,7 +78,7 @@ protected function setUp(): void { $this->client = $this->getMockBuilder(Client::class) ->setConstructorArgs([$this->config, $this->urlGenerator, $this->session, $this->logger]) - ->setMethods(['fetchURL']) + ->onlyMethods(['fetchURL']) ->getMock(); } @@ -91,7 +93,7 @@ public function testGetConfig(): void { */ public function testGetAppConfig($expectedData, $dataInConfig, $expectedErrorMessage = null): void { $this->config->method('getSystemValue')->willReturn('from system config'); - $this->config->expects(self::once())->method('getAppValue')->willReturnCallback(function ($app, $key, $default) use ($dataInConfig) { + $this->config->expects(self::once())->method('getAppValue')->willReturnCallback(function () use ($dataInConfig) { return $dataInConfig; }); if ($expectedErrorMessage) { @@ -101,6 +103,10 @@ public function testGetAppConfig($expectedData, $dataInConfig, $expectedErrorMes self::assertEquals($expectedData, $return); } + /** + * @throws OpenIDConnectClientException + * @throws JsonException + */ public function testGetWellKnown(): void { $this->client->setProviderURL('https://example.net'); $this->client->expects(self::once())->method('fetchURL')->with('https://example.net/.well-known/openid-configuration')->willReturn('{"foo": "bar"}'); @@ -108,6 +114,9 @@ public function testGetWellKnown(): void { self::assertEquals((object)['foo' => 'bar'], $return); } + /** + * @throws OpenIDConnectClientException + */ public function testCtor(): void { $providerUrl = 'https://example.net'; @@ -132,7 +141,7 @@ public function testCtor(): void { }); $this->client = $this->getMockBuilder(Client::class) ->setConstructorArgs([$this->config, $this->urlGenerator, $this->session, $this->logger]) - ->setMethods(['fetchURL']) + ->onlyMethods(['fetchURL']) ->getMock(); self::assertEquals($providerUrl, $this->client->getProviderURL()); @@ -140,6 +149,9 @@ public function testCtor(): void { self::assertEquals(true, $this->client->getVerifyPeer()); } + /** + * @throws OpenIDConnectClientException + */ public function testCtorInsecure(): void { $providerUrl = 'https://example.net'; @@ -165,7 +177,7 @@ public function testCtorInsecure(): void { }); $this->client = $this->getMockBuilder(Client::class) ->setConstructorArgs([$this->config, $this->urlGenerator, $this->session, $this->logger]) - ->setMethods(['fetchURL']) + ->onlyMethods(['fetchURL']) ->getMock(); self::assertEquals($providerUrl, $this->client->getProviderURL()); @@ -176,15 +188,16 @@ public function testCtorInsecure(): void { /** * @dataProvider providesGetUserInfoData * @param $useAccessTokenPayloadForUserInfo + * @throws JsonException + * @throws OpenIDConnectClientException */ public function testGetUserInfo($useAccessTokenPayloadForUserInfo): void { - $this->config->method('getSystemValue')->willReturnCallback(static function ($key) use ($useAccessTokenPayloadForUserInfo) { + $this->config->method('getSystemValue')->willReturnCallback(static function ($key) { if ($key === 'openid-connect') { return [ 'provider-url' => '$providerUrl', 'client-id' => 'client-id', 'client-secret' => 'secret', - 'use-access-token-payload-for-user-info' => $useAccessTokenPayloadForUserInfo ]; } if ($key === 'proxy') { @@ -202,18 +215,18 @@ public function testGetUserInfo($useAccessTokenPayloadForUserInfo): void { ->getMock(); if ($useAccessTokenPayloadForUserInfo) { $this->client->expects(self::never())->method('requestUserInfo'); - $this->client->expects(self::once())->method('getAccessTokenPayload')->willReturn([ + $this->client->expects(self::once())->method('getAccessTokenPayload')->willReturn((object)[ 'preferred_username' => 'alice@example.net' ]); } else { - $this->client->expects(self::never())->method('getAccessTokenPayload'); - $this->client->expects(self::once())->method('requestUserInfo')->willReturn([ + $this->client->expects(self::once())->method('getAccessTokenPayload')->willReturn(null); + $this->client->expects(self::once())->method('requestUserInfo')->willReturn((object)[ 'preferred_username' => 'alice@example.net' ]); } $info = $this->client->getUserInfo(); - self::assertEquals([ + self::assertEquals((object)[ 'preferred_username' => 'alice@example.net' ], $info); } diff --git a/tests/unit/EventHandlerTest.php b/tests/unit/EventHandlerTest.php index a5150fc..7252580 100644 --- a/tests/unit/EventHandlerTest.php +++ b/tests/unit/EventHandlerTest.php @@ -36,37 +36,25 @@ class EventHandlerTest extends TestCase { - /** - * @var MockObject | ISession - */ - private $session; /** * @var MockObject | EventHandler */ private $eventHandler; - /** - * @var MockObject | IUserSession - */ - private $userSession; /** * @var MockObject | EventDispatcherInterface */ private $dispatcher; - /** - * @var MockObject | IRequest - */ - private $request; protected function setUp(): void { parent::setUp(); $this->dispatcher = $this->createMock(EventDispatcherInterface::class); - $this->request = $this->createMock(IRequest::class); - $this->session = $this->createMock(ISession::class); - $this->userSession = $this->createMock(IUserSession::class); + $request = $this->createMock(IRequest::class); + $session = $this->createMock(ISession::class); + $userSession = $this->createMock(IUserSession::class); $this->eventHandler = $this->getMockBuilder(EventHandler::class) - ->setConstructorArgs([$this->dispatcher, $this->request, $this->userSession, $this->session]) - ->setMethods(['createAuthBackend']) + ->setConstructorArgs([$this->dispatcher, $request, $userSession, $session]) + ->onlyMethods(['createAuthBackend']) ->getMock(); } diff --git a/tests/unit/LoggerTest.php b/tests/unit/LoggerTest.php index 2229b69..aa1bfa9 100644 --- a/tests/unit/LoggerTest.php +++ b/tests/unit/LoggerTest.php @@ -22,6 +22,7 @@ namespace OCA\OpenIdConnect\Tests\Unit; +use InvalidArgumentException; use OCA\OpenIdConnect\Logger; use OCP\ILogger; use PHPUnit\Framework\MockObject\MockObject; @@ -71,7 +72,7 @@ public function testLog(): void { } public function testLogException(): void { - $ex = new \InvalidArgumentException(); + $ex = new InvalidArgumentException(); $this->innerLogger->expects(self::once())->method('logException')->with($ex, ['app' => 'OpenID']); $this->logger->logException($ex); } diff --git a/tests/unit/OpenIdConnectAuthModuleTest.php b/tests/unit/OpenIdConnectAuthModuleTest.php index f5cabc0..b277c5b 100644 --- a/tests/unit/OpenIdConnectAuthModuleTest.php +++ b/tests/unit/OpenIdConnectAuthModuleTest.php @@ -31,8 +31,11 @@ use OCA\OpenIdConnect\Service\AutoProvisioningService; use OCA\OpenIdConnect\Service\UserLookupService; use OCP\ICacheFactory; +use OCP\IConfig; use OCP\ILogger; use OCP\IRequest; +use OCP\ISession; +use OCP\IURLGenerator; use OCP\IUser; use OCP\IUserManager; use PHPUnit\Framework\MockObject\MockObject; @@ -44,10 +47,6 @@ class OpenIdConnectAuthModuleTest extends TestCase { * @var OpenIdConnectAuthModule */ private $authModule; - /** - * @var MockObject | IUserManager - */ - private $manager; /** * @var MockObject | ILogger */ @@ -71,14 +70,22 @@ class OpenIdConnectAuthModuleTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->manager = $this->createMock(IUserManager::class); + $manager = $this->createMock(IUserManager::class); $this->logger = $this->createMock(ILogger::class); $this->cacheFactory = $this->createMock(ICacheFactory::class); $this->lookupService = $this->createMock(UserLookupService::class); - $this->client = $this->createMock(Client::class); + + $config = $this->createMock(IConfig::class); + $generator = $this->createMock(IURLGenerator::class); + $session = $this->createMock(ISession::class); + $this->client = $this->getMockBuilder(Client::class) + ->onlyMethods(['getUserInfo', 'refreshToken', 'signOut', 'getOpenIdConfig', 'verifyJWTsignature', 'getAccessTokenPayload', 'setAccessToken', 'introspectToken', 'getAutoProvisionConfig']) + ->setConstructorArgs([$config, $generator, $session, $this->logger]) + ->getMock(); + $this->autoProvisioningService = $this->createMock(AutoProvisioningService::class); $this->authModule = new OpenIdConnectAuthModule( - $this->manager, + $manager, $this->logger, $this->cacheFactory, $this->lookupService, @@ -87,6 +94,9 @@ protected function setUp(): void { ); } + /** + * @throws LoginException + */ public function testNoBearer(): void { $request = $this->createMock(IRequest::class); @@ -94,6 +104,9 @@ public function testNoBearer(): void { self::assertNull($return); } + /** + * @throws LoginException + */ public function testNotConfigured(): void { $request = $this->createMock(IRequest::class); $request->method('getHeader')->willReturn('Bearer 1234567890'); @@ -102,8 +115,13 @@ public function testNotConfigured(): void { self::assertNull($return); } + /** + * @throws LoginException + */ public function testInvalidToken(): void { $this->client->method('getOpenIdConfig')->willReturn([]); + $this->client->method('getAccessTokenPayload')->willReturn((object)[]); + $this->client->method('verifyJWTsignature')->willReturn(false); $this->cacheFactory->method('create')->willReturn(new ArrayCache()); $request = $this->createMock(IRequest::class); $request->method('getHeader')->willReturn('Bearer 1234567890'); @@ -113,6 +131,9 @@ public function testInvalidToken(): void { self::assertNull($return); } + /** + * @throws LoginException + */ public function testInvalidTokenWithIntrospection(): void { $this->client->method('getOpenIdConfig')->willReturn(['use-token-introspection-endpoint' => true]); $this->client->method('introspectToken')->willReturn((object)['error' => 'expired']); @@ -125,6 +146,9 @@ public function testInvalidTokenWithIntrospection(): void { self::assertNull($return); } + /** + * @throws LoginException + */ public function testInvalidTokenWithIntrospectionNotActive(): void { $this->client->method('getOpenIdConfig')->willReturn(['use-token-introspection-endpoint' => true]); $this->client->method('introspectToken')->willReturn((object)['active' => false]); @@ -137,6 +161,9 @@ public function testInvalidTokenWithIntrospectionNotActive(): void { self::assertNull($return); } + /** + * @throws LoginException + */ public function testValidTokenWithIntrospection(): void { $this->client->method('getOpenIdConfig')->willReturn(['use-token-introspection-endpoint' => true]); $this->client->method('introspectToken')->willReturn((object)['active' => true, 'exp' => \time() + 3600]); @@ -151,6 +178,9 @@ public function testValidTokenWithIntrospection(): void { self::assertEquals($user, $return); } + /** + * @throws LoginException + */ public function testValidTokenWithJWT(): void { $this->client->method('getOpenIdConfig')->willReturn(['use-token-introspection-endpoint' => false]); $this->client->method('verifyJWTsignature')->willReturn(true); @@ -181,6 +211,9 @@ public function testExpiredCachedToken(): void { $this->authModule->auth($request); } + /** + * @throws LoginException + */ public function testValidTokenWithAutoUpdate(): void { $userInfo = (object)['email' => 'foo@example.com']; $openIdConfig = ['auto-provision' => [ 'update' => ['enabled' => true ]]]; diff --git a/tests/unit/SessionVerifierTest.php b/tests/unit/SessionVerifierTest.php index f264efb..8cf171f 100644 --- a/tests/unit/SessionVerifierTest.php +++ b/tests/unit/SessionVerifierTest.php @@ -22,13 +22,17 @@ namespace OCA\OpenIdConnect\Tests\Unit; +use JsonException; +use Jumbojett\OpenIDConnectClientException; use OC\HintException; use OCA\OpenIdConnect\Client; use OCA\OpenIdConnect\Logger; use OCA\OpenIdConnect\SessionVerifier; use OCP\ICache; use OCP\ICacheFactory; +use OCP\IConfig; use OCP\ISession; +use OCP\IURLGenerator; use OCP\IUserSession; use PHPUnit\Framework\MockObject\MockObject; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -40,10 +44,6 @@ class SessionVerifierTest extends TestCase { * @var SessionVerifier */ private $sessionVerifier; - /** - * @var MockObject | EventDispatcherInterface - */ - private $dispatcher; /** * @var MockObject | ISession */ @@ -52,10 +52,6 @@ class SessionVerifierTest extends TestCase { * @var MockObject | IUserSession */ private $userSession; - /** - * @var MockObject | Logger - */ - private $logger; /** * @var MockObject | ICacheFactory */ @@ -67,16 +63,27 @@ class SessionVerifierTest extends TestCase { protected function setUp(): void { parent::setUp(); - $this->dispatcher = $this->createMock(EventDispatcherInterface::class); + $dispatcher = $this->createMock(EventDispatcherInterface::class); $this->session = $this->createMock(ISession::class); $this->userSession = $this->createMock(IUserSession::class); - $this->logger = $this->createMock(Logger::class); + $logger = $this->createMock(Logger::class); $this->cacheFactory = $this->createMock(ICacheFactory::class); - $this->client = $this->createMock(Client::class); - $this->sessionVerifier = new SessionVerifier($this->logger, $this->session, $this->userSession, $this->cacheFactory, $this->dispatcher, $this->client); + $config = $this->createMock(IConfig::class); + $generator = $this->createMock(IURLGenerator::class); + $this->client = $this->getMockBuilder(Client::class) + ->onlyMethods(['refreshToken', 'signOut', 'getOpenIdConfig', 'verifyJWTsignature', 'getAccessTokenPayload', 'setAccessToken', 'introspectToken']) + ->setConstructorArgs([$config, $generator, $this->session, $logger]) + ->getMock(); + + $this->sessionVerifier = new SessionVerifier($logger, $this->session, $this->userSession, $this->cacheFactory, $dispatcher, $this->client); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testOpenIdSessionExpired(): void { $this->session->method('get')->with('oca.openid-connect.session-id')->willReturn('SID-1234'); $cache = $this->createMock(ICache::class); @@ -88,6 +95,11 @@ public function testOpenIdSessionExpired(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testNoAccessTokenInSession(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -99,6 +111,11 @@ public function testNoAccessTokenInSession(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testValidCachedAccessToken(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -113,6 +130,11 @@ public function testValidCachedAccessToken(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testInvalidCachedAccessTokenNoRefresh(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -128,6 +150,11 @@ public function testInvalidCachedAccessTokenNoRefresh(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testInvalidCachedAccessTokenRefresh(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -146,6 +173,10 @@ public function testInvalidCachedAccessTokenRefresh(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws JsonException + */ public function testInvalidCachedAccessTokenRefreshError(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -166,6 +197,11 @@ public function testInvalidCachedAccessTokenRefreshError(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testValidFreshAccessToken(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -184,6 +220,11 @@ public function testValidFreshAccessToken(): void { $this->sessionVerifier->verifySession(); } + /** + * @throws OpenIDConnectClientException + * @throws HintException + * @throws JsonException + */ public function testValidFreshAccessTokenWithIntrospection(): void { $this->session->method('get')->withConsecutive( ['oca.openid-connect.session-id'], @@ -202,7 +243,7 @@ public function testValidFreshAccessTokenWithIntrospection(): void { $this->sessionVerifier->verifySession(); } - public function provideOpenIdConfig() { + public function provideOpenIdConfig(): array { return [ [null, null], [[], null], @@ -215,8 +256,9 @@ public function provideOpenIdConfig() { * @dataProvider provideOpenIdConfig * @param string[]|null $openIdConfig * @param string $expectedLogoutRedirectUri + * @throws JsonException */ - public function testLogoutRedirect($openIdConfig, $expectedLogoutRedirectUri) { + public function testLogoutRedirect($openIdConfig, $expectedLogoutRedirectUri): void { $this->client->method('getOpenIdConfig') ->willReturn($openIdConfig); $this->client->expects($this->once())