Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add API Platform compatibility #1098

Merged
merged 1 commit into from
Dec 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 testCheckOpenApiExportCommand()
{
$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