diff --git a/.gitignore b/.gitignore index b1a9bef1..42e746dc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ phpunit.xml Tests/autoload.php +var/ vendor/ Propel/om/ Propel/map/ diff --git a/Controller/AuthorizeController.php b/Controller/AuthorizeController.php index 07febaa3..f973597e 100644 --- a/Controller/AuthorizeController.php +++ b/Controller/AuthorizeController.php @@ -167,9 +167,9 @@ public function authorizeAction(Request $request) 'client' => $this->getClient(), ]; - return $this->twig->render( - '@FOSOAuthServer/Authorize/authorize.html.twig', - $data + return new Response( + $this->twig->render('@FOSOAuthServer/Authorize/authorize.html.twig', $data), + Response::HTTP_OK ); } diff --git a/Form/Type/AuthorizeFormType.php b/Form/Type/AuthorizeFormType.php index 08aec45f..cfecf2a1 100644 --- a/Form/Type/AuthorizeFormType.php +++ b/Form/Type/AuthorizeFormType.php @@ -41,6 +41,7 @@ public function configureOptions(OptionsResolver $resolver) { $resolver->setDefaults([ 'data_class' => 'FOS\OAuthServerBundle\Form\Model\Authorize', + 'validation_groups' => [], ]); } diff --git a/Resources/doc/configuration_reference.md b/Resources/doc/configuration_reference.md index 9529fa32..deade0e9 100644 --- a/Resources/doc/configuration_reference.md +++ b/Resources/doc/configuration_reference.md @@ -50,8 +50,6 @@ fos_oauth_server: # Enforce state to be passed in authorization (see RFC 6749, section 10.12) #enforce_state: true or false - template: - engine: twig ``` [Back to index](index.md) diff --git a/Tests/Controller/AuthorizeControllerFunctionalTest.php b/Tests/Controller/AuthorizeControllerFunctionalTest.php new file mode 100644 index 00000000..40c76c31 --- /dev/null +++ b/Tests/Controller/AuthorizeControllerFunctionalTest.php @@ -0,0 +1,85 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\OAuthServerBundle\Tests\Controller; + +use FOS\OAuthServerBundle\Tests\Functional\TestCase; +use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken; +use Symfony\Component\Security\Core\Exception\AccessDeniedException; +use Symfony\Component\Security\Core\User\UserInterface; +use Symfony\Component\Security\Guard\Token\PostAuthenticationGuardToken; + +class AuthorizeControllerFunctionalTest extends TestCase +{ + public function setUp(): void + { + parent::setUp(); + + $this->client = $this->createClient(); + } + + public function tearDown(): void + { + unset($this->client); + + parent::tearDown(); + } + + public function testAuthorizeActionWillThrowAccessDeniedException(): void + { + self::$kernel->getContainer()->get('security.token_storage')->setToken(new AnonymousToken('test-secret', 'anon')); + + $this->expectException(AccessDeniedException::class); + $this->expectExceptionMessage('This user does not have access to this section.'); + + $this->client->catchExceptions(false); + $this->client->request('GET', '/oauth/v2/auth'); + } + + public function testAuthorizeActionWillRenderTemplate(): void + { + $user = $this->getMockBuilder(UserInterface::class) + ->disableOriginalConstructor() + ->getMock() + ; + + self::$kernel->getContainer()->get('security.token_storage')->setToken( + new PostAuthenticationGuardToken($user, 'member_area', ['ROLE_USER']) + ); + + $this->client->catchExceptions(false); + $this->client->request('GET', '/oauth/v2/auth', [ + 'client_id' => '123_test-client-id', + ]); + + $this->assertResponse(200, '
'); + } + + public function testAuthorizeActionWillFinishClientAuthorization(): void + { + // TODO: refactor unit AuthorizeControllerTest as functional test here + $this->assertTrue(true); + } + + public function testAuthorizeActionWillEnsureLogout(): void + { + // TODO: refactor unit AuthorizeControllerTest as functional test here + $this->assertTrue(true); + } + + public function testAuthorizeActionWillProcessAuthorizationForm(): void + { + // TODO: refactor unit AuthorizeControllerTest as functional test here + $this->assertTrue(true); + } +} diff --git a/Tests/Controller/AuthorizeControllerTest.php b/Tests/Controller/AuthorizeControllerTest.php index 255d4c08..171730f9 100644 --- a/Tests/Controller/AuthorizeControllerTest.php +++ b/Tests/Controller/AuthorizeControllerTest.php @@ -221,106 +221,6 @@ public function setUp(): void parent::setUp(); } - public function testAuthorizeActionWillThrowAccessDeniedException(): void - { - $token = $this->getMockBuilder(TokenInterface::class) - ->disableOriginalConstructor() - ->getMock() - ; - - $this->tokenStorage - ->expects($this->at(0)) - ->method('getToken') - ->willReturn($token) - ; - - $token - ->expects($this->at(0)) - ->method('getUser') - ->willReturn(null) - ; - - $this->expectException(AccessDeniedException::class); - $this->expectExceptionMessage('This user does not have access to this section.'); - - $this->instance->authorizeAction($this->request); - } - - public function testAuthorizeActionWillRenderTemplate(): void - { - $token = $this->getMockBuilder(TokenInterface::class) - ->disableOriginalConstructor() - ->getMock() - ; - - $this->tokenStorage - ->expects($this->at(0)) - ->method('getToken') - ->willReturn($token) - ; - - $token - ->expects($this->at(0)) - ->method('getUser') - ->willReturn($this->user) - ; - - $this->session - ->expects($this->at(0)) - ->method('get') - ->with('_fos_oauth_server.ensure_logout') - ->willReturn(false) - ; - - $propertyReflection = new ReflectionProperty(AuthorizeController::class, 'client'); - $propertyReflection->setAccessible(true); - $propertyReflection->setValue($this->instance, $this->client); - - $this->eventDispatcher - ->expects($this->at(0)) - ->method('dispatch') - ->with(new OAuthEvent($this->user, $this->client), OAuthEvent::PRE_AUTHORIZATION_PROCESS) - ->willReturn($this->event) - ; - - $this->event - ->expects($this->at(0)) - ->method('isAuthorizedClient') - ->with() - ->willReturn(false) - ; - - $this->authorizeFormHandler - ->expects($this->at(0)) - ->method('process') - ->with() - ->willReturn(false) - ; - - $this->form - ->expects($this->at(0)) - ->method('createView') - ->willReturn($this->formView) - ; - - $response = ''; - - $this->twig - ->expects($this->at(0)) - ->method('render') - ->with( - '@FOSOAuthServer/Authorize/authorize.html.twig', - [ - 'form' => $this->formView, - 'client' => $this->client, - ] - ) - ->willReturn($response) - ; - - self::assertSame($response, $this->instance->authorizeAction($this->request)); - } - public function testAuthorizeActionWillFinishClientAuthorization(): void { $token = $this->getMockBuilder(TokenInterface::class) @@ -462,8 +362,6 @@ public function testAuthorizeActionWillEnsureLogout(): void ->willReturn($this->formView) ; - $response = ''; - $this->twig ->expects($this->at(0)) ->method('render') @@ -474,10 +372,12 @@ public function testAuthorizeActionWillEnsureLogout(): void 'client' => $this->client, ] ) - ->willReturn($response) + ->willReturn('') ; - self::assertSame($response, $this->instance->authorizeAction($this->request)); + $response = $this->instance->authorizeAction($this->request); + self::assertInstanceOf(Response::class, $response); + self::assertSame('', $response->getContent()); } public function testAuthorizeActionWillProcessAuthorizationForm(): void diff --git a/Tests/DependencyInjection/Security/Factory/OAuthFactoryTest.php b/Tests/DependencyInjection/Security/Factory/OAuthFactoryTest.php index ed28f8b8..1c4333e8 100644 --- a/Tests/DependencyInjection/Security/Factory/OAuthFactoryTest.php +++ b/Tests/DependencyInjection/Security/Factory/OAuthFactoryTest.php @@ -20,6 +20,7 @@ use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\DefinitionDecorator; use Symfony\Component\DependencyInjection\Reference; /** @@ -46,7 +47,7 @@ class OAuthFactoryTest extends TestCase public function setUp(): void { - $this->definitionDecoratorClass = 'Symfony\Component\DependencyInjection\DefinitionDecorator'; + $this->definitionDecoratorClass = DefinitionDecorator::class; $this->childDefinitionClass = ChildDefinition::class; $this->instance = new OAuthFactory(); @@ -64,19 +65,6 @@ public function testGetKey(): void self::assertSame('fos_oauth', $this->instance->getKey()); } - public function testCreate(): void - { - if (class_exists($this->childDefinitionClass)) { - return $this->useChildDefinition(); - } - - if (class_exists($this->definitionDecoratorClass)) { - return $this->useDefinitionDecorator(); - } - - throw new Exception('Neither DefinitionDecorator nor ChildDefinition exist'); - } - public function testAddConfigurationDoesNothing(): void { $nodeDefinition = $this->getMockBuilder(NodeDefinition::class) @@ -86,6 +74,17 @@ public function testAddConfigurationDoesNothing(): void self::assertNull($this->instance->addConfiguration($nodeDefinition)); } + public function testCreate(): void + { + if (class_exists($this->childDefinitionClass)) { + $this->useChildDefinition(); + } elseif (class_exists($this->definitionDecoratorClass)) { + $this->useDefinitionDecorator(); + } else { + throw new Exception('Neither DefinitionDecorator nor ChildDefinition exist'); + } + } + protected function useDefinitionDecorator(): void { $container = $this->getMockBuilder(ContainerBuilder::class) diff --git a/Tests/Form/Type/AuthorizeFormTypeTest.php b/Tests/Form/Type/AuthorizeFormTypeTest.php index 41bc7523..40750fc6 100644 --- a/Tests/Form/Type/AuthorizeFormTypeTest.php +++ b/Tests/Form/Type/AuthorizeFormTypeTest.php @@ -85,6 +85,7 @@ public function testConfigureOptionsWillSetDefaultsOnTheOptionsResolver(): void ->method('setDefaults') ->with([ 'data_class' => Authorize::class, + 'validation_groups' => [], ]) ->willReturn($resolver) ; diff --git a/Tests/Functional/AppKernel.php b/Tests/Functional/AppKernel.php index e97ffc18..196007b9 100644 --- a/Tests/Functional/AppKernel.php +++ b/Tests/Functional/AppKernel.php @@ -21,12 +21,12 @@ class AppKernel extends Kernel public function registerBundles() { $bundles = [ + new \FOS\OAuthServerBundle\FOSOAuthServerBundle(), + new \FOS\OAuthServerBundle\Tests\Functional\TestBundle\TestBundle(), new \Symfony\Bundle\FrameworkBundle\FrameworkBundle(), + new \Symfony\Bundle\MonologBundle\MonologBundle(), new \Symfony\Bundle\SecurityBundle\SecurityBundle(), new \Symfony\Bundle\TwigBundle\TwigBundle(), - new \FOS\OAuthServerBundle\FOSOAuthServerBundle(), - - new \FOS\OAuthServerBundle\Tests\Functional\TestBundle\TestBundle(), ]; if ('orm' === $this->getEnvironment()) { diff --git a/Tests/Functional/TestBundle/Manager/AccessTokenManager.php b/Tests/Functional/TestBundle/Manager/AccessTokenManager.php new file mode 100644 index 00000000..a1fcec86 --- /dev/null +++ b/Tests/Functional/TestBundle/Manager/AccessTokenManager.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager; + +use FOS\OAuthServerBundle\Model\AccessToken; +use FOS\OAuthServerBundle\Model\AccessTokenManagerInterface; +use FOS\OAuthServerBundle\Model\TokenInterface; +use FOS\OAuthServerBundle\Model\TokenManager; + +class AccessTokenManager extends TokenManager implements AccessTokenManagerInterface +{ + /** + * {@inheritdoc} + */ + public function findTokenBy(array $criteria): ?AccessToken + { + // create an instance as if found + $accessToken = new AccessToken(); + + return $accessToken; + } + + /** + * {@inheritdoc} + */ + public function findTokenByToken($token): ?AccessToken + { + // create an instance as if found + $accessToken = new AccessToken(); + $accessToken->setToken($token); + + return $accessToken; + } + + /** + * {@inheritdoc} + */ + public function getClass(): string + { + return self::class; + } + + /** + * {@inheritdoc} + */ + public function updateToken(TokenInterface $token): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteToken(TokenInterface $token): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteExpired(): void + { + } +} diff --git a/Tests/Functional/TestBundle/Manager/AuthCodeManager.php b/Tests/Functional/TestBundle/Manager/AuthCodeManager.php new file mode 100644 index 00000000..85429672 --- /dev/null +++ b/Tests/Functional/TestBundle/Manager/AuthCodeManager.php @@ -0,0 +1,73 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager; + +use FOS\OAuthServerBundle\Model\AuthCode; +use FOS\OAuthServerBundle\Model\AuthCodeInterface; +use FOS\OAuthServerBundle\Model\AuthCodeManager as BaseAuthCodeManager; + +class AuthCodeManager extends BaseAuthCodeManager +{ + /** + * {@inheritdoc} + */ + public function findAuthCodeBy(array $criteria): AuthCode + { + // create an instance as if found + $authCode = new AuthCode(); + + return $authCode; + } + + /** + * {@inheritdoc} + */ + public function findAuthCodeByToken($token) + { + // create an instance as if found + $authCode = new AuthCode(); + $authCode->setToken($token); + + return $authCode; + } + + /** + * {@inheritdoc} + */ + public function getClass(): string + { + return self::class; + } + + /** + * {@inheritdoc} + */ + public function updateAuthCode(AuthCodeInterface $authCode): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteAuthCode(AuthCodeInterface $authCode): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteExpired(): void + { + } +} diff --git a/Tests/Functional/TestBundle/Manager/ClientManager.php b/Tests/Functional/TestBundle/Manager/ClientManager.php new file mode 100644 index 00000000..e500a1db --- /dev/null +++ b/Tests/Functional/TestBundle/Manager/ClientManager.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager; + +use FOS\OAuthServerBundle\Model\Client; +use FOS\OAuthServerBundle\Model\ClientInterface; +use FOS\OAuthServerBundle\Model\ClientManager as BaseClientManager; +use ReflectionClass; + +class ClientManager extends BaseClientManager +{ + /** + * {@inheritdoc} + */ + public function findClientBy(array $criteria): ?Client + { + // create an instance as if found + $client = new Client(); + + return $client; + } + + /** + * {@inheritdoc} + */ + public function findClientByPublicId($publicId): ?Client + { + if (false === $pos = mb_strpos($publicId, '_')) { + return null; + } + + $id = mb_substr($publicId, 0, $pos); + $randomId = mb_substr($publicId, $pos + 1); + + // create an instance as if found + $client = new Client(); + $client->setRandomId($randomId); + + $reflectionClass = new ReflectionClass($client); + $reflectionProperty = $reflectionClass->getProperty('id'); + $reflectionProperty->setAccessible(true); + $reflectionProperty->setValue($client, $id); + + return $client; + } + + /** + * {@inheritdoc} + */ + public function getClass(): string + { + return self::class; + } + + /** + * {@inheritdoc} + */ + public function updateClient(ClientInterface $client): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteClient(ClientInterface $client): void + { + } +} diff --git a/Tests/Functional/TestBundle/Manager/RefreshTokenManager.php b/Tests/Functional/TestBundle/Manager/RefreshTokenManager.php new file mode 100644 index 00000000..d3b09532 --- /dev/null +++ b/Tests/Functional/TestBundle/Manager/RefreshTokenManager.php @@ -0,0 +1,74 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager; + +use FOS\OAuthServerBundle\Model\RefreshToken; +use FOS\OAuthServerBundle\Model\RefreshTokenManagerInterface; +use FOS\OAuthServerBundle\Model\TokenInterface; +use FOS\OAuthServerBundle\Model\TokenManager; + +class RefreshTokenManager extends TokenManager implements RefreshTokenManagerInterface +{ + /** + * {@inheritdoc} + */ + public function findTokenBy(array $criteria): ?RefreshToken + { + // create an instance as if found + $refreshToken = new RefreshToken(); + + return $refreshToken; + } + + /** + * {@inheritdoc} + */ + public function findTokenByToken($token): ?RefreshToken + { + // create an instance as if found + $refreshToken = new RefreshToken(); + $refreshToken->setToken($token); + + return $refreshToken; + } + + /** + * {@inheritdoc} + */ + public function getClass(): string + { + return self::class; + } + + /** + * {@inheritdoc} + */ + public function updateToken(TokenInterface $token): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteToken(TokenInterface $token): void + { + } + + /** + * {@inheritdoc} + */ + public function deleteExpired(): void + { + } +} diff --git a/Tests/Functional/TestCase.php b/Tests/Functional/TestCase.php index a8e1612f..b53e37df 100644 --- a/Tests/Functional/TestCase.php +++ b/Tests/Functional/TestCase.php @@ -13,6 +13,8 @@ namespace FOS\OAuthServerBundle\Tests\Functional; +use LogicException; +use Symfony\Bundle\FrameworkBundle\KernelBrowser; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpKernel\KernelInterface; @@ -20,9 +22,9 @@ abstract class TestCase extends WebTestCase { /** - * @var KernelInterface|null + * @var KernelBrowser */ - protected static $kernel; + protected $client; protected function setUp(): void { @@ -30,15 +32,57 @@ protected function setUp(): void $fs->remove(sys_get_temp_dir().'/FOSOAuthServerBundle/'); } - protected function tearDown(): void + /** + * Client response assertion of status code and response content. + */ + protected function assertResponse(int $statusCode, string $content, bool $fullFailOutput = false): void { - static::$kernel = null; - } + if (!($this->client instanceof KernelBrowser)) { + throw new LogicException('Test attempts to check response, but client does not exist; use createClient() to set the test case client property.'); + } - protected static function createKernel(array $options = []) - { - $env = @$options['env'] ?: 'test'; + $this->assertSame( + $statusCode, + $this->client->getResponse()->getStatusCode(), + sprintf('Failed asserting that response status code "%d" is "%d".', $this->client->getResponse()->getStatusCode(), $statusCode) + ); + + $responseContent = $this->client->getResponse()->getContent(); + + if ('' === $responseContent && '' === $content) { + $this->assertTrue(true); + return; + } + + if ('' === $responseContent) { + $this->fail(sprintf('Response content is empty, expected "%s".', $content)); + } elseif ('' === $content) { + + // this differs from assertStringContainsString, which does not + // fail on an empty string expectation + $this->fail($fullFailOutput || strlen($responseContent) < 100 + ? sprintf('Failed asserting that response "%s" is empty.', $responseContent) + : sprintf( + 'Failed asserting that response "%s ... %s" is empty.', + substr($responseContent, 0, 40), + substr($responseContent, strlen($responseContent) - 40) + ) + ); + } - return new AppKernel($env, true); + // not using assertStringContainsString to avoid full HTML doc in the + // fail message + if (mb_strpos($responseContent, $content) === false) { + $this->fail($fullFailOutput || strlen($responseContent) < 100 + ? sprintf('Failed asserting that response "%s" contains "%s".', $responseContent, $content) + : sprintf( + 'Failed asserting that response "%s ... %s" contains "%s".', + substr($responseContent, 0, 40), + substr($responseContent, strlen($responseContent) - 40), + $content + ) + ); + } + $this->assertTrue(true); } } diff --git a/Tests/Functional/config/config.yml b/Tests/Functional/config/config.yml index cc31e483..b89acc43 100644 --- a/Tests/Functional/config/config.yml +++ b/Tests/Functional/config/config.yml @@ -1,11 +1,19 @@ framework: form: ~ secret: test + test: true router: - resource: "%kernel.project_dir%/config/routing.yml" + resource: "%kernel.project_dir%/Tests/Functional/config/routing.yml" fos_oauth_server: +monolog: + handlers: + main: + type: stream + path: '%kernel.logs_dir%/%kernel.environment%.log' + level: debug + security: role_hierarchy: ROLE_ADMIN: ROLE_USER diff --git a/Tests/Functional/config/config_test.yml b/Tests/Functional/config/config_test.yml new file mode 100644 index 00000000..cc6427b8 --- /dev/null +++ b/Tests/Functional/config/config_test.yml @@ -0,0 +1,25 @@ +imports: + - { resource: config.yml } + +fos_oauth_server: + db_driver: custom + service: + client_manager: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager\ClientManager + access_token_manager: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager\AccessTokenManager + refresh_token_manager: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager\RefreshTokenManager + auth_code_manager: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Manager\AuthCodeManager + user_provider: security.user.provider.concrete.main + + client_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\Client + access_token_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\AccessToken + refresh_token_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\RefreshToken + auth_code_class: FOS\OAuthServerBundle\Tests\Functional\TestBundle\Entity\AuthCode + +services: + # autowiring + _defaults: + autowire: true + autoconfigure: true + FOS\OAuthServerBundle\Tests\Functional\TestBundle\: + resource: '%kernel.project_dir%/Tests/Functional/TestBundle/*' + exclude: '%kernel.project_dir%/Tests/Functional/TestBundle/Entity' diff --git a/Tests/Functional/config/routing.yml b/Tests/Functional/config/routing.yml index e69de29b..8d64ac6c 100644 --- a/Tests/Functional/config/routing.yml +++ b/Tests/Functional/config/routing.yml @@ -0,0 +1,4 @@ +fos_oauth_server_token: + resource: '@FOSOAuthServerBundle/Resources/config/routing/token.xml' +fos_oauth_server_authorize: + resource: '@FOSOAuthServerBundle/Resources/config/routing/authorize.xml' diff --git a/composer.json b/composer.json index 48ef5b02..45945c8d 100644 --- a/composer.json +++ b/composer.json @@ -42,9 +42,11 @@ "phpunit/phpunit": "^8.5", "propel/propel1": "^1.7", "roave/security-advisories": "dev-master", + "symfony/browser-kit": "^5.0", "symfony/class-loader": "^3.4", "symfony/console": "^5.0", "symfony/form": "^5.0", + "symfony/monolog-bundle": "^3.5", "symfony/phpunit-bridge": "^5.0", "symfony/twig-bundle": "^5.0", "symfony/yaml": "^5.0", diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 5192b311..79c1174d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -6,6 +6,10 @@ + + + + ./