diff --git a/composer.json b/composer.json
index 62ee260..30a4125 100644
--- a/composer.json
+++ b/composer.json
@@ -23,8 +23,7 @@
"simplesamlphp/simplesamlphp-test-framework": "^1.7",
"phpunit/phpunit": "^10",
"psalm/plugin-phpunit": "^0.19.0",
- "squizlabs/php_codesniffer": "^3.7",
- "dg/bypass-finals": "^1.8"
+ "squizlabs/php_codesniffer": "^3.7"
},
"autoload": {
"psr-4": {
@@ -49,7 +48,7 @@
},
"scripts": {
"validate": [
- "vendor/bin/phpunit --no-coverage",
+ "vendor/bin/phpunit --no-coverage --testdox",
"vendor/bin/phpcs -p",
"vendor/bin/psalm --no-cache"
],
diff --git a/docs/APPLE.md b/docs/APPLE.md
index 7246c80..2a5533a 100644
--- a/docs/APPLE.md
+++ b/docs/APPLE.md
@@ -77,4 +77,8 @@ docker run --name ssp-apple-oidc \
Edit your `/etc/hosts` file to make `apple.test.idpproxy.illinois.edu` route to local host and then visit
`https://apple.test.idpproxy.illinois.edu/simplesaml/module.php/core/authenticate.php?as=appleTest` to
-initiate a login to Apple. Non-secret values such as keyId and teamId
\ No newline at end of file
+initiate a login to Apple. Non-secret values such as keyId and teamId
+
+# Documentation
+
+* [TN3107: Resolving Sign in with Apple response errors](https://developer.apple.com/documentation/technotes/tn3107-resolving-sign-in-with-apple-response-errors)
\ No newline at end of file
diff --git a/phpunit.xml b/phpunit.xml
index 0a8f4ea..c60752c 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -8,10 +8,6 @@
displayDetailsOnTestsThatTriggerDeprecations="true"
backupGlobals="false"
>
-
-
-
-
./tests
diff --git a/src/Controller/OIDCLogoutController.php b/src/Controller/OIDCLogoutController.php
index 9c731cf..22b1f5d 100644
--- a/src/Controller/OIDCLogoutController.php
+++ b/src/Controller/OIDCLogoutController.php
@@ -12,6 +12,8 @@
use SimpleSAML\Logger;
use SimpleSAML\Module\authoauth2\Auth\Source\OAuth2;
use SimpleSAML\Module\authoauth2\Auth\Source\OpenIDConnect;
+use SimpleSAML\Module\authoauth2\Codebooks\LegacyRoutesEnum;
+use SimpleSAML\Module\authoauth2\Codebooks\RoutesEnum;
use SimpleSAML\Module\authoauth2\Controller\Traits\RequestTrait;
use SimpleSAML\Module\authoauth2\locators\SourceServiceLocator;
use Symfony\Component\HttpFoundation\Request;
@@ -55,9 +57,8 @@ public function __construct(Configuration $config = null)
*/
public function loggedout(Request $request): void
{
- Logger::debug('authoauth2: logout request=' . var_export($request->request->all(), true));
-
$this->parseRequest($request);
+ Logger::debug('authoauth2: logout request=' . var_export($this->requestParams, true));
\assert(\is_array($this->state));
@@ -72,19 +73,22 @@ public function loggedout(Request $request): void
*/
public function logout(Request $request): void
{
- Logger::debug('authoauth2: logout request=' . var_export($request->request->all(), true));
+ $this->parseRequestParamsSingleton($request);
+ Logger::debug('authoauth2: logout request=' . var_export($this->requestParams, true));
// Find the authentication source
- if (!$request->query->has('authSource')) {
+ if (!isset($this->requestParams['authSource'])) {
throw new BadRequest('No authsource in the request');
}
- $sourceId = $request->query->get('authSource');
+ $sourceId = $this->requestParams['authSource'];
if (empty($sourceId) || !\is_string($sourceId)) {
throw new BadRequest('Authsource ID invalid');
}
+ $logoutRoute = $this->config->getOptionalBoolean('useLegacyRoutes', false) ?
+ LegacyRoutesEnum::LegacyLogout->value : RoutesEnum::Logout->value;
$this->getAuthSource($sourceId)
->logout([
'oidc:localLogout' => true,
- 'ReturnTo' => $this->config->getBasePath() . 'logout.php',
+ 'ReturnTo' => $this->config->getBasePath() . $logoutRoute,
]);
}
diff --git a/src/Controller/Oauth2Controller.php b/src/Controller/Oauth2Controller.php
index 359c0df..701d948 100644
--- a/src/Controller/Oauth2Controller.php
+++ b/src/Controller/Oauth2Controller.php
@@ -51,9 +51,8 @@ public function __construct()
*/
public function linkback(Request $request): void
{
- Logger::debug('authoauth2: linkback request=' . var_export($request->query->all(), true));
-
$this->parseRequest($request);
+ Logger::debug('authoauth2: linkback request=' . var_export($this->requestParams, true));
// Required for psalm
\assert($this->source instanceof OAuth2);
@@ -61,12 +60,14 @@ public function linkback(Request $request): void
\assert(\is_string($this->sourceId));
// Handle Identify Provider error
- if (!$request->query->has('code') || empty($request->query->get('code'))) {
+ if (empty($this->requestParams['code'])) {
$this->handleError($this->source, $this->state, $request);
+ // Used to facilitate testing
+ return;
}
try {
- $this->source->finalStep($this->state, (string)$request->query->get('code'));
+ $this->source->finalStep($this->state, (string)$this->requestParams['code']);
} catch (IdentityProviderException $e) {
// phpcs:ignore Generic.Files.LineLength.TooLong
Logger::error("authoauth2: error in '$this->sourceId' msg '{$e->getMessage()}' body '" . var_export($e->getResponseBody(), true) . "'");
diff --git a/src/Controller/Traits/ErrorTrait.php b/src/Controller/Traits/ErrorTrait.php
index 53b4bb5..c47806a 100644
--- a/src/Controller/Traits/ErrorTrait.php
+++ b/src/Controller/Traits/ErrorTrait.php
@@ -5,6 +5,7 @@
namespace SimpleSAML\Module\authoauth2\Controller\Traits;
use Symfony\Component\HttpFoundation\Request;
+use SimpleSAML\Module\authoauth2\Lib\RequestUtilities;
trait ErrorTrait
{
@@ -15,16 +16,17 @@ trait ErrorTrait
*/
public function parseError(Request $request): array
{
+ $requestParams = RequestUtilities::getRequestParams($request);
// Do not throw if errors are suppressed by @ operator
// error_reporting() value for suppressed errors changed in PHP 8.0.0
$error = '';
$error_description = '';
- if ($request->query->has('error')) {
- $error = (string)$request->query->get('error');
+ if (isset($requestParams['error'])) {
+ $error = (string)$requestParams['error'];
}
- if ($request->query->has('error_description')) {
- $error_description = (string)$request->query->get('error_description');
+ if (isset($requestParams['error_description'])) {
+ $error_description = (string)$requestParams['error_description'];
}
return [
diff --git a/src/Controller/Traits/RequestTrait.php b/src/Controller/Traits/RequestTrait.php
index 3ca5df6..2fd5711 100644
--- a/src/Controller/Traits/RequestTrait.php
+++ b/src/Controller/Traits/RequestTrait.php
@@ -9,8 +9,9 @@
use SimpleSAML\Error\BadRequest;
use SimpleSAML\Error\NoState;
use SimpleSAML\Module\authoauth2\Auth\Source\OAuth2;
-use SimpleSAML\Module\authoauth2\Codebooks\RoutesEnum;
use SimpleSAML\Module\authoauth2\Codebooks\LegacyRoutesEnum;
+use SimpleSAML\Module\authoauth2\Codebooks\RoutesEnum;
+use SimpleSAML\Module\authoauth2\Lib\RequestUtilities;
use SimpleSAML\Module\authoauth2\locators\SourceServiceLocator;
use Symfony\Component\HttpFoundation\Request;
@@ -36,6 +37,9 @@ trait RequestTrait
*/
protected string $expectedStateAuthId = OAuth2::AUTHID;
+ /** @var array */
+ protected array $requestParams = [];
+
/**
* @param Request $request
*
@@ -43,11 +47,13 @@ trait RequestTrait
*/
public function stateIsValid(Request $request): bool
{
- if (!$request->query->has('state')) {
+ // Parse the request parameters
+ $this->parseRequestParamsSingleton($request);
+ if (!isset($this->requestParams['state'])) {
return false;
}
/** @var ?string $stateId */
- $stateId = $request->query->get('state');
+ $stateId = $this->requestParams['state'];
if (empty($stateId)) {
return false;
}
@@ -72,7 +78,7 @@ public function parseRequest(Request $request): void
};
throw new BadRequest($message);
}
- $stateIdWithPrefix = (string)($request->query->get('state') ?? '');
+ $stateIdWithPrefix = (string)($this->requestParams['state'] ?? '');
$stateId = substr($stateIdWithPrefix, \strlen($this->expectedPrefix));
$this->state = $this->loadState($stateId, $this->expectedStageState);
@@ -110,4 +116,14 @@ public function loadState(string $id, string $stage, bool $allowMissing = false)
{
return State::loadState($id, $stage, $allowMissing);
}
+
+ /**
+ * @param Request $request
+ */
+ public function parseRequestParamsSingleton(Request $request): void
+ {
+ if (empty($this->requestParams)) {
+ $this->requestParams = RequestUtilities::getRequestParams($request);
+ }
+ }
}
diff --git a/src/Lib/RequestUtilities.php b/src/Lib/RequestUtilities.php
new file mode 100644
index 0000000..66bf56f
--- /dev/null
+++ b/src/Lib/RequestUtilities.php
@@ -0,0 +1,27 @@
+isMethod('GET')) {
+ $params = $request->query->all();
+ } elseif ($request->isMethod('POST')) {
+ $params = $request->request->all();
+ }
+
+ return $params;
+ }
+}
diff --git a/tests/lib/Controller/OIDCLogoutControllerTest.php b/tests/lib/Controller/OIDCLogoutControllerTest.php
index f8a90a7..e983549 100644
--- a/tests/lib/Controller/OIDCLogoutControllerTest.php
+++ b/tests/lib/Controller/OIDCLogoutControllerTest.php
@@ -5,17 +5,17 @@
namespace SimpleSAML\Module\authoauth2\Tests\Controller;
use DG\BypassFinals;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\MockObject\Exception;
use PHPUnit\Framework\TestCase;
-use SimpleSAML\Auth\Source;
-use SimpleSAML\Configuration;
+use SimpleSAML\Auth\Simple;
use SimpleSAML\Error\BadRequest;
use SimpleSAML\Module\authoauth2\Auth\Source\OAuth2;
use SimpleSAML\Module\authoauth2\Auth\Source\OpenIDConnect;
+use SimpleSAML\Module\authoauth2\Codebooks\RoutesEnum;
use SimpleSAML\Module\authoauth2\Controller\OIDCLogoutController;
use SimpleSAML\Module\authoauth2\Controller\Traits\RequestTrait;
use SimpleSAML\Module\authoauth2\locators\SourceService;
-use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\HttpFoundation\Request;
// Unless we declare the class here, it is not recognized by phpcs
@@ -23,21 +23,6 @@ class OIDCLogoutControllerMock extends OIDCLogoutController
{
use RequestTrait;
- public function setSource(Source $source): void
- {
- $this->source = $source;
- }
-
- public function setState(array $state): void
- {
- $this->state = $state;
- }
-
- public function setSourceId(string $sourceId): void
- {
- $this->sourceId = $sourceId;
- }
-
public function getExpectedStageState(): string
{
return $this->expectedStageState;
@@ -54,37 +39,25 @@ class OIDCLogoutControllerTest extends TestCase
{
/** @var OIDCLogoutControllerMock */
private $controller;
- /** @var Request */
- private $requestMock;
- /** @var Configuration */
- private $configMock;
- private array $stateMock;
/** @var SourceService */
private $sourceServiceMock;
+ /** @var \PHPUnit\Framework\MockObject\MockObject|(OAuth2&\PHPUnit\Framework\MockObject\MockObject) */
+ private $oauth2Mock;
+ /** @var \PHPUnit\Framework\MockObject\MockObject|(Simple&\PHPUnit\Framework\MockObject\MockObject) */
+ private $simpleMock;
+ private array $stateMock;
+ private array $parametersMock;
/**
* @throws Exception
*/
protected function setUp(): void
{
- BypassFinals::enable(bypassReadOnly: false);
-
- $this->configMock = $this->createMock(Configuration::class);
+ $this->parametersMock = ['state' => OAuth2::STATE_PREFIX . '-statefoo'];
+ $this->stateMock = [OAuth2::AUTHID => 'testSourceId'];
- // Initial state setup
- $this->stateMock = ['state' => 'testState'];
-
- $this->controller = $this->getMockBuilder(OIDCLogoutControllerMock::class)
- ->setConstructorArgs([$this->configMock])
- ->onlyMethods(['parseRequest', 'getSourceService', 'getAuthSource'])
- ->getMock();
-
- $this->requestMock = $this->getMockBuilder(Request::class)->getMock();
-
- $this->controller->method('getSourceService')
- ->willReturn($this->createMock(SourceService::class));
-
- $this->sourceServiceMock = $this->controller->getSourceService();
+ // Create the mock controller
+ $this->createControllerMock(['getSourceService', 'loadState', 'getAuthSource']);
}
public function testExpectedConstVariables(): void
@@ -93,11 +66,26 @@ public function testExpectedConstVariables(): void
$this->assertEquals(OAuth2::STATE_PREFIX . '-', $this->controller->getExpectedPrefix());
}
- public function testLoggedOutSuccess(): void
+ public static function requestMethod(): array
{
- $this->controller->setState($this->stateMock);
+ return [
+ 'GET' => ['GET'],
+ 'POST' => ['POST'],
+ ];
+ }
- $this->requestMock->request = $this->createRequestMock([]);
+ #[DataProvider('requestMethod')]
+ public function testLoggedOutSuccess(string $requestMethod): void
+ {
+ $parameters = [
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
+ );
/** @psalm-suppress UndefinedMethod,MixedMethodCall */
$this->sourceServiceMock
@@ -105,68 +93,112 @@ public function testLoggedOutSuccess(): void
->method('completeLogout')
->with($this->stateMock);
- $this->controller->loggedout($this->requestMock);
-
-
- // Assertions to verify behavior
+ $this->controller->loggedout($request);
}
- public function testLogoutWithoutAuthSourceThrowsBadRequest(): void
+ #[DataProvider('requestMethod')]
+ public function testLogoutWithoutAuthSourceThrowsBadRequest(string $requestMethod): void
{
- $this->requestMock->query = $this->createQueryMock([]);
- $this->requestMock->request = $this->createRequestMock([]);
+ $parameters = [
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
+ );
$this->expectException(BadRequest::class);
$this->expectExceptionMessage('No authsource in the request');
- $this->controller->logout($this->requestMock);
+ $this->controller->logout($request);
}
- public function testLogoutWithInvalidAuthSourceThrowsBadRequest(): void
+ #[DataProvider('requestMethod')]
+ public function testLogoutWithInvalidAuthSourceThrowsBadRequest(string $requestMethod): void
{
- $this->requestMock->query = $this->createQueryMock(['authSource' => '']);
- $this->requestMock->request = $this->createRequestMock([]);
+ $parameters = [
+ 'authSource' => ['INVALID SOURCE ID'],
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
+ );
$this->expectException(BadRequest::class);
$this->expectExceptionMessage('Authsource ID invalid');
- $this->controller->logout($this->requestMock);
+ $this->controller->logout($request);
}
- // Mock helper function
- private function createQueryMock(array $params): InputBag
+ #[DataProvider('requestMethod')]
+ public function testSuccessfullLogout(string $requestMethod): void
{
- $queryMock = $this->getMockBuilder(InputBag::class)->getMock();
- $queryMock->method('has')->willReturnCallback(
- function (string $key) use ($params) {
- return array_key_exists($key, $params);
- }
+ $parameters = [
+ 'authSource' => 'authsourceid',
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
);
- $queryMock->method('get')->willReturnCallback(
- function (?string $key) use ($params) {
- return $params[$key] ?? null;
- }
- );
- return $queryMock;
+ $logoutConfig = [
+ 'oidc:localLogout' => true,
+ 'ReturnTo' => '/' . RoutesEnum::Logout->value
+ ];
+
+ /** @psalm-suppress UndefinedMethod,MixedMethodCall */
+ $this->simpleMock
+ ->expects($this->once())
+ ->method('logout')
+ ->with($logoutConfig);
+
+ $this->controller->logout($request);
}
// Mock helper function
- private function createRequestMock(array $params): InputBag
+ private function createControllerMock(array $methods): void
{
- $queryMock = $this->getMockBuilder(InputBag::class)->getMock();
+ $this->oauth2Mock = $this->getMockBuilder(OAuth2::class)
+ ->disableOriginalConstructor()
+ ->getMock();
- $queryMock->method('get')->willReturnCallback(
- function (?string $key) use ($params) {
- return $params[$key] ?? null;
- }
- );
+ $this->simpleMock = $this->getMockBuilder(Simple::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['logout'])
+ ->getMock();
- $queryMock->method('all')->willReturnCallback(
- function (?string $key) use ($params) {
- return $params[$key] ?? [];
- }
- );
- return $queryMock;
+ $this->sourceServiceMock = $this->getMockBuilder(SourceService::class)
+ ->onlyMethods(['completeLogout', 'getById'])
+ ->getMock();
+
+ $this->controller = $this->getMockBuilder(OIDCLogoutControllerMock::class)
+ ->onlyMethods($methods)
+ ->getMock();
+
+ /** @psalm-suppress UndefinedMethod,MixedMethodCall */
+ $this->controller
+ ->method('getSourceService')
+ ->willReturn($this->sourceServiceMock);
+
+ $this->controller
+ ->method('getAuthSource')
+ ->willReturn($this->simpleMock);
+
+ $this->sourceServiceMock
+ ->method('getById')
+ ->with('testSourceId', OAuth2::class)
+ ->willReturn($this->oauth2Mock);
+
+ $this->controller
+ ->method('loadState')
+ ->willReturn($this->stateMock);
}
}
diff --git a/tests/lib/Controller/Oauth2ControllerTest.php b/tests/lib/Controller/Oauth2ControllerTest.php
index 2dfaef1..b01f83a 100644
--- a/tests/lib/Controller/Oauth2ControllerTest.php
+++ b/tests/lib/Controller/Oauth2ControllerTest.php
@@ -6,8 +6,8 @@
use DG\BypassFinals;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
+use PHPUnit\Framework\Attributes\DataProvider;
use PHPUnit\Framework\TestCase;
-use SimpleSAML\Auth\Source;
use SimpleSAML\Configuration;
use SimpleSAML\Error\AuthSource;
use SimpleSAML\Error\Exception;
@@ -20,7 +20,6 @@
use SimpleSAML\Module\authoauth2\locators\SourceService;
use SimpleSAML\Module\authoauth2\locators\SourceServiceLocator;
use SimpleSAML\Utils\HTTP;
-use Symfony\Component\HttpFoundation\InputBag;
use Symfony\Component\HttpFoundation\Request;
// Unless we declare the class here, it is not recognized by phpcs
@@ -31,31 +30,6 @@ class Oauth2ControllerMock extends Oauth2Controller
use RequestTrait;
use ErrorTrait;
- public function handleError(OAuth2 $source, array $state, Request $request): void
- {
- parent::handleError($source, $state, $request);
- }
-
- public function setSource(Source $source): void
- {
- $this->source = $source;
- }
-
- public function getSource(): ?Source
- {
- return $this->source;
- }
-
- public function setState(array $state): void
- {
- $this->state = $state;
- }
-
- public function setSourceId(string $sourceId): void
- {
- $this->sourceId = $sourceId;
- }
-
public function getExpectedStageState(): string
{
return $this->expectedStageState;
@@ -72,67 +46,110 @@ class Oauth2ControllerTest extends TestCase
{
/** @var Oauth2ControllerMock */
private $controller;
- /** @var Request */
- private $requestMock;
/** @var HTTP */
private $httpMock;
/** @var \PHPUnit\Framework\MockObject\MockObject|(OAuth2&\PHPUnit\Framework\MockObject\MockObject) */
private $oauth2Mock;
+ /** @var SourceService */
+ private $sourceServiceMock;
private array $stateMock;
+ private array $parametersMock;
protected function setUp(): void
{
- BypassFinals::enable(bypassReadOnly: false);
+ $this->oauth2Mock = $this->getMockBuilder(OAuth2::class)
+ ->disableOriginalConstructor()
+ ->onlyMethods(['finalStep', 'getConfig'])
+ ->getMock();
+ $this->sourceServiceMock = $this->getMockBuilder(SourceService::class)
+ ->onlyMethods(['getById', 'completeAuth'])
+ ->getMock();
+
- $this->requestMock = $this->getMockBuilder(Request::class)->getMock();
- $this->oauth2Mock = $this->getMockBuilder(OAuth2::class)->disableOriginalConstructor()->getMock();
$this->httpMock = $this->getMockBuilder(HTTP::class)->getMock();
- $this->stateMock = ['state' => 'testState'];
+ $this->parametersMock = ['state' => OAuth2::STATE_PREFIX . '|statefoo'];
+ $this->stateMock = [OAuth2::AUTHID => 'testSourceId'];
}
public function testExpectedConstVariables(): void
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'handleError', 'getHttp', 'parseError']);
+ $this->createControllerMock(['getSourceService', 'loadState']);
$this->assertEquals(OAuth2::STAGE_INIT, $this->controller->getExpectedStageState());
$this->assertEquals(OAuth2::STATE_PREFIX . '|', $this->controller->getExpectedPrefix());
}
- public function testLinkbackValidCode(): void
+ public static function requestMethod(): array
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'handleError', 'getHttp', 'parseError']);
- $this->requestMock->query = $this->createQueryMock(['code' => 'validCode']);
+ return [
+ 'GET' => ['GET'],
+ 'POST' => ['POST'],
+ ];
+ }
+
+ #[DataProvider('requestMethod')]
+ public function testLinkbackValidCode(string $requestMethod): void
+ {
+ $this->createControllerMock(['getSourceService', 'loadState', 'getHttp']);
+ $parameters = [
+ 'code' => 'validCode',
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
+ );
$this->oauth2Mock->expects($this->once())
->method('finalStep')
->with($this->stateMock, 'validCode');
- /** @psalm-suppress UndefinedMethod,MixedMethodCall */
- $this->controller
- ->method('getSourceService')
- ->willReturn($this->createMock(SourceService::class));
-
- $this->controller->linkback($this->requestMock);
+ $this->sourceServiceMock->expects($this->once())
+ ->method('completeAuth')
+ ->with($this->stateMock);
- // Assertions for the success scenario can be done here
+ $this->controller->linkback($request);
}
- public function testLinkbackWithNoCode(): void
+ #[DataProvider('requestMethod')]
+ public function testLinkbackWithNoCode(string $requestMethod): void
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'handleError', 'getHttp', 'parseError']);
- $this->requestMock->query = $this->createQueryMock([]);
+ $this->createControllerMock(['getSourceService', 'loadState', 'handleError']);
+
+ $parameters = [
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
+ );
/** @psalm-suppress UndefinedMethod,MixedMethodCall */
$this->controller
->expects($this->once())
->method('handleError');
- $this->controller->linkback($this->requestMock);
+ $this->controller->linkback($request);
}
- public function testLinkbackWithIdentityProviderException(): void
+ #[DataProvider('requestMethod')]
+ public function testLinkbackWithIdentityProviderException(string $requestMethod): void
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'handleError', 'getHttp', 'parseError']);
- $this->requestMock->query = $this->createQueryMock(['code' => 'validCode']);
+ $this->createControllerMock(['getSourceService', 'loadState', 'getHttp']);
+
+ $parameters = [
+ 'code' => 'validCode',
+ ...$this->parametersMock,
+ ];
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: $parameters,
+ );
$this->oauth2Mock->expects($this->once())
->method('finalStep')
@@ -140,36 +157,60 @@ public function testLinkbackWithIdentityProviderException(): void
$this->expectException(AuthSource::class);
- $this->controller->linkback($this->requestMock);
+ $this->controller->linkback($request);
+ }
+
+ public static function configuration(): array
+ {
+ return [ //datasets
+ 'useConsentPage (GET)' => [ // dataset 0
+ [
+ ['useConsentErrorPage' => true],
+ ],
+ 'GET'
+ ],
+ 'useConsentPage & legacyRoute (GET)' => [ // data set 1
+ [
+ ['useConsentErrorPage' => true, 'useLegacyRoutes' => true],
+ ],
+ 'GET'
+ ],
+ 'useConsentPage (POST)' => [ // dataset 0
+ [
+ ['useConsentErrorPage' => true],
+ ],
+ 'POST'
+ ],
+ 'useConsentPage & legacyRoute (POST)' => [ // data set 1
+ [
+ ['useConsentErrorPage' => true, 'useLegacyRoutes' => true],
+ ],
+ 'POST'
+ ],
+ ];
}
/**
* @throws Exception
*/
- public function testHandleErrorWithConsentedError(): void
+ #[DataProvider('configuration')]
+ public function testHandleErrorWithConsentedError(array $configuration, string $requestMethod): void
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'getHttp', 'parseError']);
- $this->controller->getSource()->method('getAuthId')->willReturn('authId');
- $this->controller
- ->getSource()
- ->method('getConfig')
- ->willReturn(new class (['useConsentErrorPage' => true], '') extends Configuration {
- public function getOptionalBoolean($name, $default): bool
- {
- if (!$this->hasValue($name) && isset($default)) {
- return filter_var($default, FILTER_VALIDATE_BOOLEAN);
- }
- return filter_var($this->getValue($name), FILTER_VALIDATE_BOOLEAN);
- }
- });
-
- $this->requestMock->query = $this->createQueryMock(
- ['error' => 'invalid_scope', 'error_description' => 'Invalid scope']
+ $this->createControllerMock(['getSourceService', 'loadState', 'getHttp']);
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: [
+ ...$this->parametersMock,
+ 'error' => 'invalid_scope',
+ 'error_description' => 'Invalid scope',
+ ],
);
- $this->controller->method('parseError')
- ->with($this->requestMock)
- ->willReturn(['invalid_scope', 'Invalid scope']);
+ $this->oauth2Mock
+ ->method('getConfig')
+ ->willReturn(new Configuration($configuration, 'test'));
$this->controller->method('getHttp')->willReturn($this->httpMock);
@@ -177,107 +218,91 @@ public function getOptionalBoolean($name, $default): bool
->method('redirectTrustedURL')
->with('http://localhost/module.php/authoauth2/errors/consent');
- $this->controller->handleError($this->controller->getSource(), $this->stateMock, $this->requestMock);
+ $this->controller->linkback($request);
}
- public function testHandleErrorWithConsentErrorAtLegacyRoute(): void
+ public static function oauth2errors(): array
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'getHttp', 'parseError']);
- $this->controller->getSource()->method('getAuthId')->willReturn('authId');
- $configArray = ['useConsentErrorPage' => true, 'useLegacyRoutes' => true];
- $this->controller
- ->getSource()
- ->method('getConfig')
- ->willReturn(new Configuration($configArray, 'test'));
-
- $this->requestMock->query = $this->createQueryMock(
- ['error' => 'invalid_scope', 'error_description' => 'Invalid scope']
- );
-
- $this->controller->method('parseError')
- ->with($this->requestMock)
- ->willReturn(['invalid_scope', 'Invalid scope']);
-
- $this->controller->method('getHttp')->willReturn($this->httpMock);
-
- $this->httpMock->expects($this->once())
- ->method('redirectTrustedURL')
- ->with('http://localhost/module.php/authoauth2/errors/consent.php');
-
- $this->controller->handleError($this->controller->getSource(), $this->stateMock, $this->requestMock);
+ return [
+ 'oauth2 valid error code (GET)' => [
+ [
+ 'error' => 'invalid_scope',
+ 'error_description' => 'Invalid scope'
+ ],
+ UserAborted::class,
+ 'GET'
+ ],
+ 'oauth2 invalid error code (GET)' => [
+ [
+ 'error' => 'invalid_error',
+ 'error_description' => 'Invalid error'
+ ],
+ AuthSource::class,
+ 'GET'
+ ],
+ 'oauth2 valid error code (POST)' => [
+ [
+ 'error' => 'invalid_scope',
+ 'error_description' => 'Invalid scope'
+ ],
+ UserAborted::class,
+ 'POST'
+ ],
+ 'oauth2 invalid error code(POST)' => [
+ [
+ 'error' => 'invalid_error',
+ 'error_description' => 'Invalid error'
+ ],
+ AuthSource::class,
+ 'POST'
+ ]
+ ];
}
- public function testHandleErrorWithUserAborted(): void
+ #[DataProvider('oauth2errors')]
+ public function testHandleErrorThrowException(array $errorResponse, string $className, string $requestMethod): void
{
- $this->createControllerMock(['parseRequest', 'getSourceService', 'getHttp', 'parseError']);
- $this->controller->getSource()->method('getAuthId')->willReturn('authId');
+ $this->createControllerMock(['getSourceService', 'loadState', 'getHttp']);
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: $requestMethod,
+ parameters: [
+ ...$this->parametersMock,
+ ...$errorResponse,
+ ],
+ );
$configArray = ['useConsentErrorPage' => false];
- $this->controller
- ->getSource()
+
+ $this->oauth2Mock
->method('getConfig')
->willReturn(new Configuration($configArray, 'test'));
- $this->requestMock->query = $this->createQueryMock(
- ['error' => 'invalid_scope', 'error_description' => 'Invalid scope']
- );
-
- $this->controller->method('parseError')
- ->with($this->requestMock)
- ->willReturn(['invalid_scope', 'Invalid scope']);
-
- $this->controller->method('getHttp')->willReturn($this->httpMock);
-
- $this->expectException(UserAborted::class);
-
- $this->controller->handleError($this->controller->getSource(), $this->stateMock, $this->requestMock);
- }
-
-
- public function testHandleErrorWithAuthSourceException(): void
- {
- $this->createControllerMock(['parseRequest', 'getSourceService', 'getHttp', 'parseError']);
- $this->controller->getSource()->method('getAuthId')->willReturn('authId');
-
- $this->requestMock->query = $this->createQueryMock(
- ['error' => 'invalid_error', 'error_description' => 'Invalid Error']
- );
-
- $this->controller->method('parseError')
- ->with($this->requestMock)
- ->willReturn(['invalid_error', 'Invalid Error']);
-
$this->controller->method('getHttp')->willReturn($this->httpMock);
- $this->expectException(AuthSource::class);
+ $this->expectException($className);
- $this->controller->handleError($this->controller->getSource(), $this->stateMock, $this->requestMock);
+ $this->controller->linkback($request);
}
- private function createControllerMock(array $methods): void
+ protected function createControllerMock(array $methods): void
{
$this->controller = $this->getMockBuilder(Oauth2ControllerMock::class)
->onlyMethods($methods)
->getMock();
- // Stubbing dependencies
- $this->controller->setSource($this->oauth2Mock);
- $this->controller->setState($this->stateMock);
- $this->controller->setSourceId('testSourceId');
- }
+ /** @psalm-suppress UndefinedMethod,MixedMethodCall */
+ $this->controller
+ ->method('getSourceService')
+ ->willReturn($this->sourceServiceMock);
- private function createQueryMock(array $params): InputBag
- {
- $queryMock = $this->getMockBuilder(InputBag::class)->getMock();
- $queryMock->method('has')->willReturnCallback(
- function (string $key) use ($params) {
- return array_key_exists($key, $params);
- }
- );
- $queryMock->method('get')->willReturnCallback(
- function (string $key) use ($params) {
- return $params[$key] ?? null;
- }
- );
- return $queryMock;
+ $this->sourceServiceMock
+ ->method('getById')
+ ->with('testSourceId', OAuth2::class)
+ ->willReturn($this->oauth2Mock);
+
+ $this->controller
+ ->method('loadState')
+ ->willReturn($this->stateMock);
}
}
diff --git a/tests/lib/Controller/Trait/RequestTraitTest.php b/tests/lib/Controller/Trait/RequestTraitTest.php
index c0125ac..f5356de 100644
--- a/tests/lib/Controller/Trait/RequestTraitTest.php
+++ b/tests/lib/Controller/Trait/RequestTraitTest.php
@@ -147,7 +147,6 @@ public function testParseRequestWithEmptyAuthSourceId(): void
$this->controller->parseRequest($this->request);
}
-
public function testParseRequestWithInvalidSource(): void
{
$this->request->query->set('state', OAuth2::STATE_PREFIX . '|valid_state_with_invalid_source');
@@ -166,4 +165,27 @@ public function testParseRequestWithInvalidSource(): void
$this->controller->parseRequest($this->request);
}
+
+ public function testParsePostGetRequest(): void
+ {
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: 'POST',
+ parameters: [ 'error' => 'invalid_request'],
+ );
+
+ $expected = ['error' => 'invalid_request'];
+
+ $this->parseRequestParamsSingleton($request);
+ $this->assertEquals($expected, $this->requestParams);
+
+ $request = Request::create(
+ uri: 'https://localhost/auth/authorize',
+ method: 'GET',
+ parameters: [ 'error' => 'invalid_request']
+ );
+
+ $this->parseRequestParamsSingleton($request);
+ $this->assertEquals($expected, $this->requestParams);
+ }
}