Skip to content

Commit

Permalink
feature #1112 fix: better API Platform and json_login compatibility (…
Browse files Browse the repository at this point in the history
…alanpoulain)

This PR was merged into the 2.x branch.

Discussion
----------

fix: better API Platform and json_login compatibility

After #1098.

This PR adds two new arguments for the API Platform compatibility: `username_path` and `password_path`.

Moreover, it is not necessary anymore to add `check_path` to enable the decoration of the OpenAPI factory: if API Platform is detected and if a `json_login` firewall is available, it is automatically enabled!

For complex configuration, the user can take over the configuration.

Commits
-------

f6f4f6f fix: better API Platform and json_login compatibility
  • Loading branch information
chalasr committed Feb 8, 2023
2 parents 407de37 + f6f4f6f commit c54d647
Show file tree
Hide file tree
Showing 9 changed files with 138 additions and 37 deletions.
52 changes: 52 additions & 0 deletions DependencyInjection/Compiler/ApiPlatformOpenApiPass.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler;

use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class ApiPlatformOpenApiPass implements CompilerPassInterface
{
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('lexik_jwt_authentication.api_platform.openapi.factory') || !$container->hasParameter('security.firewalls')) {
return;
}

$checkPath = null;
$usernamePath = null;
$passwordPath = null;
$firewalls = $container->getParameter('security.firewalls');
foreach ($firewalls as $firewallName) {
if ($container->hasDefinition('security.authenticator.json_login.' . $firewallName)) {
$firewallOptions = $container->getDefinition('security.authenticator.json_login.' . $firewallName)->getArgument(4);
$checkPath = $firewallOptions['check_path'];
$usernamePath = $firewallOptions['username_path'];
$passwordPath = $firewallOptions['password_path'];

break;
}
}

$openApiFactoryDefinition = $container->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory');
$checkPathArg = $openApiFactoryDefinition->getArgument(1);
$usernamePathArg = $openApiFactoryDefinition->getArgument(2);
$passwordPathArg = $openApiFactoryDefinition->getArgument(3);

if (!$checkPath && !$checkPathArg) {
$container->removeDefinition('lexik_jwt_authentication.api_platform.openapi.factory');

return;
}

if (!$checkPathArg) {
$openApiFactoryDefinition->replaceArgument(1, $checkPath);
}
if (!$usernamePathArg) {
$openApiFactoryDefinition->replaceArgument(2, $usernamePath ?? 'username');
}
if (!$passwordPathArg) {
$openApiFactoryDefinition->replaceArgument(3, $passwordPath ?? 'password');
}
}
}
12 changes: 10 additions & 2 deletions DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,19 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->arrayNode('api_platform')
->info('API Platform compatibility: add check_path in OpenApi documentation.')
->info('API Platform compatibility: add check_path in OpenAPI documentation.')
->children()
->scalarNode('check_path')
->defaultNull()
->info('The login check path to document on OpenApi.')
->info('The login check path to add in OpenAPI.')
->end()
->scalarNode('username_path')
->defaultNull()
->info('The path to the username in the JSON body.')
->end()
->scalarNode('password_path')
->defaultNull()
->info('The path to the password in the JSON body.')
->end()
->end()
->end()
Expand Down
13 changes: 8 additions & 5 deletions DependencyInjection/LexikJWTAuthenticationExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -144,15 +144,18 @@ public function load(array $configs, ContainerBuilder $container)
->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".');
}
if (!class_exists(ApiPlatformBundle::class) && (isset($config['api_platform']['check_path']) || isset($config['api_platform']['username_path']) || isset($config['api_platform']['password_path']))) {
throw new LogicException('API Platform cannot be detected. Try running "composer require api-platform/core".');
}

if (class_exists(ApiPlatformBundle::class)) {
$loader->load('api_platform.xml');

$container
->getDefinition('lexik_jwt_authentication.api_platform.openapi.factory')
->replaceArgument(1, $config['api_platform']['check_path']);
->replaceArgument(1, $config['api_platform']['check_path'] ?? null)
->replaceArgument(2, $config['api_platform']['username_path'] ?? null)
->replaceArgument(3, $config['api_platform']['password_path'] ?? null);
}
}

Expand Down
2 changes: 2 additions & 0 deletions LexikJWTAuthenticationBundle.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Lexik\Bundle\JWTAuthenticationBundle;

use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\ApiPlatformOpenApiPass;
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\DeprecateLegacyGuardAuthenticatorPass;
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\RegisterLegacyGuardAuthenticatorPass;
use Lexik\Bundle\JWTAuthenticationBundle\DependencyInjection\Compiler\WireGenerateTokenCommandPass;
Expand Down Expand Up @@ -32,6 +33,7 @@ public function build(ContainerBuilder $container)

$container->addCompilerPass(new WireGenerateTokenCommandPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new DeprecateLegacyGuardAuthenticatorPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);
$container->addCompilerPass(new ApiPlatformOpenApiPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 0);

/** @var SecurityExtension $extension */
$extension = $container->getExtension('security');
Expand Down
49 changes: 35 additions & 14 deletions OpenApi/OpenApiFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,15 @@ class OpenApiFactory implements OpenApiFactoryInterface
private $decorated;

private $checkPath;
private $usernamePath;
private $passwordPath;

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

/**
Expand All @@ -41,7 +45,8 @@ public function __invoke(array $context = []): OpenApi

$openApi
->getPaths()
->addPath($this->checkPath, (new PathItem())->withPost((new Operation())
->addPath($this->checkPath, (new PathItem())->withPost(
(new Operation())
->withOperationId('login_check_post')
->withTags(['Login Check'])
->withResponses([
Expand All @@ -65,22 +70,14 @@ public function __invoke(array $context = []): OpenApi
],
])
->withSummary('Creates a user token.')
->withRequestBody((new RequestBody())
->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'],
'properties' => $properties = array_merge_recursive($this->getJsonSchemaFromPathParts(explode('.', $this->usernamePath)), $this->getJsonSchemaFromPathParts(explode('.', $this->passwordPath))),
'required' => array_keys($properties),
]))),
]))
->withRequired(true)
Expand All @@ -89,4 +86,28 @@ public function __invoke(array $context = []): OpenApi

return $openApi;
}

private function getJsonSchemaFromPathParts(array $pathParts): array
{
$jsonSchema = [];

if (count($pathParts) === 1) {
$jsonSchema[array_shift($pathParts)] = [
'type' => 'string',
'nullable' => false,
];

return $jsonSchema;
}

$pathPart = array_shift($pathParts);
$properties = $this->getJsonSchemaFromPathParts($pathParts);
$jsonSchema[$pathPart] = [
'type' => 'object',
'properties' => $properties,
'required' => array_keys($properties),
];

return $jsonSchema;
}
}
4 changes: 3 additions & 1 deletion Resources/config/api_platform.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
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">
<service id="lexik_jwt_authentication.api_platform.openapi.factory" class="Lexik\Bundle\JWTAuthenticationBundle\OpenApi\OpenApiFactory" decorates="api_platform.openapi.factory" decoration-on-invalid="ignore" public="false">
<argument type="service" id="lexik_jwt_authentication.api_platform.openapi.factory.inner"/>
<argument /><!-- check path -->
<argument /><!-- username path -->
<argument /><!-- password path -->
</service>
</services>

Expand Down
11 changes: 7 additions & 4 deletions Resources/doc/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ Configure application routing
api_login_check:
path: /api/login_check
Enable API Platform compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
API Platform compatibility
~~~~~~~~~~~~~~~~~~~~~~~~~~

If `API Platform <https://api-platform.com/>`__ is detected, the integration will be done with your security configuration.

To enable the `API Platform <https://api-platform.com/>`__ compatibility, add the
``lexik_jwt_authentication.api_platform.check_path`` configuration option as following:
If you wish to change some parameters, you can do it with this configuration:

.. code-block:: yaml
Expand All @@ -158,6 +159,8 @@ To enable the `API Platform <https://api-platform.com/>`__ compatibility, add th
# ...
api_platform:
check_path: /api/login_check
username_path: email
password_path: security.credentials.password
Usage
-----
Expand Down
30 changes: 19 additions & 11 deletions Tests/Functional/Command/ApiPlatformOpenApiExportCommandTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

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;
Expand All @@ -12,7 +11,7 @@
*
* @author Vincent Chalamon <[email protected]>
*
* @requires function ApiPlatformBundle::build
* @requires function ApiPlatform\Symfony\Bundle\ApiPlatformBundle::build
*/
class ApiPlatformOpenApiExportCommandTest extends TestCase
{
Expand Down Expand Up @@ -43,10 +42,7 @@ public function testCheckOpenApiExportCommand()
"paths": {
"/login_check": {
"post": {
"deprecated": false,
"description": "",
"operationId": "login_check_post",
"parameters": [],
"requestBody": {
"description": "The login data",
"required": true,
Expand All @@ -55,18 +51,30 @@ public function testCheckOpenApiExportCommand()
"schema": {
"type": "object",
"properties": {
"_username": {
"email": {
"nullable": false,
"type": "string"
},
"_password": {
"nullable": false,
"type": "string"
"security": {
"type": "object",
"properties": {
"credentials": {
"type": "object",
"properties": {
"password": {
"nullable": false,
"type": "string"
}
},
"required": ["password"]
}
},
"required": ["credentials"]
}
},
"required": [
"_username",
"_password"
"email",
"security"
]
}
}
Expand Down
2 changes: 2 additions & 0 deletions Tests/Functional/app/AppKernel.php
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,8 @@ public function registerContainerConfiguration(LoaderInterface $loader)
$container->prependExtensionConfig('lexik_jwt_authentication', [
'api_platform' => [
'check_path' => '/login_check',
'username_path' => 'email',
'password_path' => 'security.credentials.password',
],
]);
$router['resource'] = '%kernel.root_dir%/config/routing_api_platform.yml';
Expand Down

0 comments on commit c54d647

Please sign in to comment.