Skip to content

Commit

Permalink
feat: add API Platform compatibility
Browse files Browse the repository at this point in the history
  • Loading branch information
vincentchalamon committed Dec 12, 2022
1 parent 59dd54a commit 0a87c57
Show file tree
Hide file tree
Showing 10 changed files with 302 additions and 8 deletions.
4 changes: 4 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ jobs:
if: "matrix.symfony == '6.0.*' || matrix.symfony == '6.1.*' || matrix.symfony == '6.2.*' || matrix.symfony == '6.3.*@dev'"
run: "composer remove --dev --no-update symfony/security-guard"

- name: "Install api-platform/core"
if: "matrix.symfony == '6.1.*' || matrix.symfony == '6.2.*' || matrix.symfony == '6.3.*@dev'"
run: "composer require --dev --no-update api-platform/core:^3.0"

- name: "Install dependencies"
run: "composer update ${{ matrix.composer-flags }} --prefer-dist"
env:
Expand Down
9 changes: 9 additions & 0 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,15 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end()
->arrayNode('api_platform')
->info('API Platform compatibility: add check_path in OpenApi documentation.')
->children()
->scalarNode('check_path')
->defaultNull()
->info('The login check path to document on OpenApi.')
->end()
->end()
->end()
->end();

return $treeBuilder;
Expand Down
13 changes: 13 additions & 0 deletions DependencyInjection/LexikJWTAuthenticationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection;

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Lexik\Bundle\JWTAuthenticationBundle\Encoder\JWTEncoderInterface;
use Symfony\Component\Config\Definition\Exception\InvalidConfigurationException;
use Symfony\Component\Config\FileLocator;
Expand All @@ -10,6 +11,7 @@
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
use Symfony\Component\DependencyInjection\ChildDefinition;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Exception\LogicException;
use Symfony\Component\DependencyInjection\Loader\XmlFileLoader;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
Expand Down Expand Up @@ -141,6 +143,17 @@ public function load(array $configs, ContainerBuilder $container)
->replaceArgument(3, $config['pass_phrase'])
->replaceArgument(4, $encoderConfig['signature_algorithm']);
}

if (isset($config['api_platform']['check_path'])) {
if (!class_exists(ApiPlatformBundle::class)) {
throw new LogicException('API Platform cannot be detected. Try running "composer require api-platform/core".');
}

$loader->load('api_platform.xml');
$container
->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory')
->replaceArgument(1, $config['api_platform']['check_path']);
}
}

private static function createTokenExtractors(ContainerBuilder $container, array $tokenExtractorsConfig)
Expand Down
92 changes: 92 additions & 0 deletions OpenApi/OpenApiFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\OpenApi;

use ApiPlatform\OpenApi\Factory\OpenApiFactoryInterface;
use ApiPlatform\OpenApi\Model\MediaType;
use ApiPlatform\OpenApi\Model\Operation;
use ApiPlatform\OpenApi\Model\PathItem;
use ApiPlatform\OpenApi\Model\RequestBody;
use ApiPlatform\OpenApi\OpenApi;
use Symfony\Component\HttpFoundation\Response;

/**
* Decorates API Platform OpenApiFactory.
*
* @author Vincent Chalamon <[email protected]>
*
* @final
*/
class OpenApiFactory implements OpenApiFactoryInterface
{
/**
* @var OpenApiFactoryInterface
*/
private $decorated;

private $checkPath;

public function __construct(OpenApiFactoryInterface $decorated, string $checkPath)
{
$this->decorated = $decorated;
$this->checkPath = $checkPath;
}

/**
* {@inheritdoc}
*/
public function __invoke(array $context = []): OpenApi
{
$openApi = ($this->decorated)($context);

$openApi
->getPaths()
->addPath($this->checkPath, (new PathItem())->withPost((new Operation())
->withOperationId('login_check_post')
->withTags(['Login Check'])
->withResponses([
Response::HTTP_OK => [
'description' => 'User token created',
'content' => [
'application/json' => [
'schema' => [
'type' => 'object',
'properties' => [
'token' => [
'readOnly' => true,
'type' => 'string',
'nullable' => false,
],
],
'required' => ['token'],
],
],
],
],
])
->withSummary('Creates a user token.')
->withRequestBody((new RequestBody())
->withDescription('The login data')
->withContent(new \ArrayObject([
'application/json' => new MediaType(new \ArrayObject(new \ArrayObject([
'type' => 'object',
'properties' => [
'_username' => [
'type' => 'string',
'nullable' => false,
],
'_password' => [
'type' => 'string',
'nullable' => false,
],
],
'required' => ['_username', '_password'],
]))),
]))
->withRequired(true)
)
));

return $openApi;
}
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Github Issues are dedicated to bug reports and feature requests.
Contributing
------------

See the [CONTRIBUTING](CONTRIBUTING.rst) file.
See the [CONTRIBUTING](CONTRIBUTING.md) file.


Sponsoring
Expand Down
14 changes: 14 additions & 0 deletions Resources/config/api_platform.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" ?>

<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">

<services>
<service id="lexik_jwt_authentication.api_platform.openapi.factory" class="Lexik\Bundle\JWTAuthenticationBundle\OpenApi\OpenApiFactory" decorates="api_platform.openapi.factory" public="false">
<argument type="service" id="lexik_jwt_authentication.api_platform.openapi.factory.inner"/>
<argument /><!-- check path -->
</service>
</services>

</container>
14 changes: 14 additions & 0 deletions Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,20 @@ Configure application routing
api_login_check:
path: /api/login_check
Enable API Platform compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

To enable the `API Platform <https://api-platform.com/>`__ compatibility, add the
``lexik_jwt_authentication.api_platform.check_path`` configuration option as following:

.. code-block:: yaml
# config/packages/lexik_jwt_authentication.yaml
lexik_jwt_authentication:
# ...
api_platform:
check_path: /api/login_check
Usage
-----

Expand Down
120 changes: 120 additions & 0 deletions Tests/Functional/Command/ApiPlatformOpenApiExportCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Functional\Command;

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Lexik\Bundle\JWTAuthenticationBundle\Tests\Functional\TestCase;
use Symfony\Bundle\FrameworkBundle\Console\Application;
use Symfony\Component\Console\Tester\CommandTester;

/**
* Check API Platform compatibility.
*
* @author Vincent Chalamon <[email protected]>
*
* @requires function ApiPlatformBundle::build
*/
class ApiPlatformOpenApiExportCommandTest extends TestCase
{
/**
* Test command.
*/
public function testCheckOpenSSLCommand()
{
$kernel = $this->bootKernel();
$app = new Application($kernel);
$tester = new CommandTester($app->find('api:openapi:export'));

$this->assertSame(0, $tester->execute([]));
$this->assertJsonStringEqualsJsonString(<<<JSON
{
"openapi": "3.0.0",
"info": {
"description": "API Platform integration in LexikJWTAuthenticationBundle",
"title": "LexikJWTAuthenticationBundle",
"version": "1.0.0"
},
"servers": [
{
"description": "",
"url": "/"
}
],
"paths": {
"/login_check": {
"post": {
"deprecated": false,
"description": "",
"operationId": "login_check_post",
"parameters": [],
"requestBody": {
"description": "The login data",
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"_username": {
"nullable": false,
"type": "string"
},
"_password": {
"nullable": false,
"type": "string"
}
},
"required": [
"_username",
"_password"
]
}
}
}
},
"responses": {
"200": {
"content": {
"application/json": {
"schema": {
"properties": {
"token": {
"nullable": false,
"readOnly": true,
"type": "string"
}
},
"required": [
"token"
],
"type": "object"
}
}
},
"description": "User token created"
}
},
"summary": "Creates a user token.",
"tags": [
"Login Check"
]
},
"parameters": []
}
},
"components": {
"examples": {},
"headers": {},
"parameters": {},
"requestBodies": {},
"responses": {},
"schemas": {},
"securitySchemes": {}
},
"security": [],
"tags": []
}
JSON
, $tester->getDisplay());
}
}
36 changes: 29 additions & 7 deletions Tests/Functional/app/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@

namespace Lexik\Bundle\JWTAuthenticationBundle\Tests\Functional;

use ApiPlatform\Symfony\Bundle\ApiPlatformBundle;
use Lexik\Bundle\JWTAuthenticationBundle\LexikJWTAuthenticationBundle;
use Lexik\Bundle\JWTAuthenticationBundle\Tests\Functional\Bundle\Bundle;
use Psr\Log\NullLogger;
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle;
use Symfony\Bundle\SecurityBundle\SecurityBundle\Security;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Loader\LoaderInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\HttpKernel\Kernel;
Expand Down Expand Up @@ -42,12 +42,17 @@ public function __construct($environment, $debug, $testCase = null)
*/
public function registerBundles(): array
{
return [
$bundles = [
new FrameworkBundle(),
new SecurityBundle(),
new LexikJWTAuthenticationBundle(),
new Bundle(),
];
if (class_exists(ApiPlatformBundle::class)) {
$bundles[] = new ApiPlatformBundle();
}

return $bundles;
}

public function getRootDir()
Expand Down Expand Up @@ -100,12 +105,29 @@ public function registerContainerConfiguration(LoaderInterface $loader)
});
}

$loader->load(function (ContainerBuilder $container) use ($sessionConfig) {
$router = [
'resource' => '%kernel.root_dir%/config/routing.yml',
'utf8' => true,
];
if (class_exists(ApiPlatformBundle::class)) {
$loader->load(function (ContainerBuilder $container) use (&$router) {
$container->prependExtensionConfig('api_platform', [
'title' => 'LexikJWTAuthenticationBundle',
'description' => 'API Platform integration in LexikJWTAuthenticationBundle',
'version' => '1.0.0',
]);
$container->prependExtensionConfig('lexik_jwt_authentication', [
'api_platform' => [
'check_path' => '/login_check',
],
]);
$router['resource'] = '%kernel.root_dir%/config/routing_api_platform.yml';
});
}

$loader->load(function (ContainerBuilder $container) use ($router, $sessionConfig) {
$container->prependExtensionConfig('framework', [
'router' => [
'resource' => '%kernel.root_dir%/config/routing.yml',
'utf8' => true,
],
'router' => $router,
'session' => $sessionConfig
]);
});
Expand Down
6 changes: 6 additions & 0 deletions Tests/Functional/app/config/routing_api_platform.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
default:
resource: routing.yml

api_platform:
resource: .
type: api_platform

0 comments on commit 0a87c57

Please sign in to comment.