Skip to content

Commit

Permalink
[Research] CORS (#24)
Browse files Browse the repository at this point in the history
* Add cors subscriber and config

* Apply php-cs-fixer changes

* Update event_subscriber.yaml

* Fix static analysis

* Apply php-cs-fixer changes

* Introduce trait

* Apply php-cs-fixer changes

* Adding error responses

* Apply php-cs-fixer changes

* Disable voter

---------

Co-authored-by: mattamon <mattamon@users.noreply.github.com>
  • Loading branch information
mattamon and mattamon authored Apr 22, 2024
1 parent f576568 commit 7c144f9
Show file tree
Hide file tree
Showing 20 changed files with 420 additions and 37 deletions.
6 changes: 5 additions & 1 deletion config/event_subscribers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,9 @@ services:
public: false

#Subscriber
Pimcore\Bundle\StudioApiBundle\EventSubscriber\CorsSubscriber:
tags: [ 'kernel.event_subscriber' ]

Pimcore\Bundle\StudioApiBundle\EventSubscriber\ApiExceptionSubscriber:
tags: [ 'kernel.event_subscriber' ]
tags: [ 'kernel.event_subscriber' ]
arguments: ["%kernel.environment%"]
9 changes: 7 additions & 2 deletions src/Attributes/Response/Error/BadRequestResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Error;
use OpenApi\Attributes\Schema;
use Pimcore\Bundle\StudioApiBundle\Response\Schemas;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class BadRequestResponse extends Response
Expand All @@ -29,7 +30,11 @@ public function __construct()
parent::__construct(
response: 400,
description: 'Bad Request',
content: new JsonContent(ref: Error::class, example: ['message' => 'Something bad you did'])
content: new JsonContent(
oneOf: array_map(static function ($class) {
return new Schema(ref: $class);
}, Schemas::Errors),
)
);
}
}
11 changes: 8 additions & 3 deletions src/Attributes/Response/Error/MethodNotAllowedResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Error;
use OpenApi\Attributes\Schema;
use Pimcore\Bundle\StudioApiBundle\Response\Schemas;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class MethodNotAllowedResponse extends Response
Expand All @@ -28,8 +29,12 @@ public function __construct()
{
parent::__construct(
response: 405,
description: 'Bad Request',
content: new JsonContent(ref: Error::class, example: ['message' => 'Using the wrong method you are'])
description: 'Method Not Allowed',
content: new JsonContent(
oneOf: array_map(static function ($class) {
return new Schema(ref: $class);
}, Schemas::Errors),
)
);
}
}
9 changes: 7 additions & 2 deletions src/Attributes/Response/Error/UnauthorizedResponse.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@
use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use Pimcore\Bundle\StudioApiBundle\Response\Schema\Error;
use OpenApi\Attributes\Schema;
use Pimcore\Bundle\StudioApiBundle\Response\Schemas;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class UnauthorizedResponse extends Response
Expand All @@ -29,7 +30,11 @@ public function __construct()
parent::__construct(
response: 401,
description: 'Unauthorized',
content: new JsonContent(ref: Error::class, example: ['message' => 'Computer says no'])
content: new JsonContent(
oneOf: array_map(static function ($class) {
return new Schema(ref: $class);
}, Schemas::Errors),
)
);
}
}
32 changes: 32 additions & 0 deletions src/Attributes/Response/Error/UnprocessableContentResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error;

use Attribute;
use OpenApi\Attributes\Response;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class UnprocessableContentResponse extends Response
{
public function __construct()
{
parent::__construct(
response: 422,
description: 'Unprocessable Content',
);
}
}
40 changes: 40 additions & 0 deletions src/Attributes/Response/Error/UnsupportedMediaTypeResponse.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php
declare(strict_types=1);

/**
* Pimcore
*
* This source file is available under two different licenses:
* - GNU General Public License version 3 (GPLv3)
* - Pimcore Commercial License (PCL)
* Full copyright and license information is available in
* LICENSE.md which is distributed with this source code.
*
* @copyright Copyright (c) Pimcore GmbH (http://www.pimcore.org)
* @license http://www.pimcore.org/license GPLv3 and PCL
*/

namespace Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error;

use Attribute;
use OpenApi\Attributes\JsonContent;
use OpenApi\Attributes\Response;
use OpenApi\Attributes\Schema;
use Pimcore\Bundle\StudioApiBundle\Response\Schemas;

#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_METHOD | Attribute::IS_REPEATABLE)]
final class UnsupportedMediaTypeResponse extends Response
{
public function __construct()
{
parent::__construct(
response: 415,
description: 'Unsupported Media Type',
content: new JsonContent(
oneOf: array_map(static function ($class) {
return new Schema(ref: $class);
}, Schemas::Errors),
)
);
}
}
4 changes: 4 additions & 0 deletions src/Controller/Api/Assets/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\BadRequestResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnprocessableContentResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnsupportedMediaTypeResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Property\AnyOfAsset;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
Expand Down Expand Up @@ -86,6 +88,8 @@ public function __construct(
#[BadRequestResponse]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
#[UnsupportedMediaTypeResponse]
#[UnprocessableContentResponse]
public function getAssets(#[MapQueryString] Parameters $parameters): JsonResponse
{
$assetQuery = $this->filterService->applyFilters(
Expand Down
4 changes: 4 additions & 0 deletions src/Controller/Api/Assets/GetController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Content\OneOfAssetJson;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnprocessableContentResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnsupportedMediaTypeResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
Expand Down Expand Up @@ -58,6 +60,8 @@ public function __construct(
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
#[UnsupportedMediaTypeResponse]
#[UnprocessableContentResponse]
public function getAssetById(int $id): JsonResponse
{
return $this->jsonResponse($this->assetSearchService->getAssetById($id));
Expand Down
4 changes: 4 additions & 0 deletions src/Controller/Api/Authorization/AuthorizationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Request\TokenRequestBody;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnprocessableContentResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnsupportedMediaTypeResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Controller\Api\AbstractApiController;
Expand Down Expand Up @@ -87,6 +89,8 @@ public function login(#[MapRequestPayload] Credentials $credentials): JsonRespon
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
#[UnsupportedMediaTypeResponse]
#[UnprocessableContentResponse]
public function refresh(#[MapRequestPayload] Refresh $refresh): JsonResponse
{
$tokenInfo = $this->tokenService->refreshToken($refresh->getToken());
Expand Down
4 changes: 4 additions & 0 deletions src/Controller/Api/DataObjects/CollectionController.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\BadRequestResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnprocessableContentResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnsupportedMediaTypeResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Property\DataObjectCollection;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
Expand Down Expand Up @@ -85,6 +87,8 @@ public function __construct(
#[BadRequestResponse]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
#[UnsupportedMediaTypeResponse]
#[UnprocessableContentResponse]
public function getDataObjects(#[MapQueryString] DataObjectParameters $parameters): JsonResponse
{

Expand Down
4 changes: 4 additions & 0 deletions src/Controller/Api/TranslationController.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
use Pimcore\Bundle\StudioApiBundle\Attributes\Request\TranslationRequestBody;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\MethodNotAllowedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnauthorizedResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnprocessableContentResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\Error\UnsupportedMediaTypeResponse;
use Pimcore\Bundle\StudioApiBundle\Attributes\Response\SuccessResponse;
use Pimcore\Bundle\StudioApiBundle\Config\Tags;
use Pimcore\Bundle\StudioApiBundle\Request\Query\Translation;
Expand Down Expand Up @@ -61,6 +63,8 @@ public function __construct(
)]
#[UnauthorizedResponse]
#[MethodNotAllowedResponse]
#[UnsupportedMediaTypeResponse]
#[UnprocessableContentResponse]
public function getTranslations(
#[MapRequestPayload] Translation $translation,
): JsonResponse {
Expand Down
78 changes: 64 additions & 14 deletions src/DependencyInjection/Configuration.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@

namespace Pimcore\Bundle\StudioApiBundle\DependencyInjection;

use Pimcore\Bundle\StudioApiBundle\Exception\InvalidHostException;
use Pimcore\Bundle\StudioApiBundle\Exception\InvalidPathException;
use Symfony\Component\Config\Definition\Builder\ArrayNodeDefinition;
use Symfony\Component\Config\Definition\Builder\TreeBuilder;
use Symfony\Component\Config\Definition\ConfigurationInterface;

Expand All @@ -31,28 +33,51 @@ class Configuration implements ConfigurationInterface

/**
* {@inheritdoc}
*
* @throws InvalidPathException
* @throws InvalidHostException
*/
public function getConfigTreeBuilder(): TreeBuilder
{
$treeBuilder = new TreeBuilder(self::ROOT_NODE);

$rootNode = $treeBuilder->getRootNode();
$rootNode->addDefaultsIfNotSet();
$rootNode->children()
$this->addOpenApiScanPathsNode($rootNode);
$this->addApiTokenNode($rootNode);
$this->addAllowedHostsForCorsNode($rootNode);

return $treeBuilder;
}

private function addOpenApiScanPathsNode(ArrayNodeDefinition $node): void
{
$node->children()
->arrayNode('openApiScanPaths')
->prototype('scalar')->end()
->validate()
->always(function ($paths) {
foreach ($paths as $path) {
if (!is_dir($path)) {
throw new InvalidPathException(sprintf('The path "%s" is not a valid directory.', $path));
}
}
->prototype('scalar')->end()
->validate()
->always(
function ($paths) {
foreach ($paths as $path) {
if (!is_dir($path)) {
throw new InvalidPathException(
sprintf(
'The path "%s" is not a valid directory.',
$path
)
);
}
}

return $paths;
})
->end()
->end()
return $paths;
})
->end()
->end();
}

private function addApiTokenNode(ArrayNodeDefinition $node): void
{
$node->children()
->arrayNode('api_token')
->addDefaultsIfNotSet()
->children()
Expand All @@ -61,7 +86,32 @@ public function getConfigTreeBuilder(): TreeBuilder
->end()
->end()
->end();
}

return $treeBuilder;
private function addAllowedHostsForCorsNode(ArrayNodeDefinition $node): void
{
$node->children()
->arrayNode('allowedHostsForCors')
->prototype('scalar')->end()
->validate()
->always(
/**
* @throws InvalidHostException
*/ function ($hosts) {
foreach ($hosts as $host) {
if (!filter_var($host)) {
throw new InvalidHostException(
sprintf(
'The host "%s" is not a valid url.',
$host
)
);
}
}

return $hosts;
})
->end()
->end();
}
}
4 changes: 4 additions & 0 deletions src/DependencyInjection/PimcoreStudioApiExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
namespace Pimcore\Bundle\StudioApiBundle\DependencyInjection;

use Exception;
use Pimcore\Bundle\StudioApiBundle\EventSubscriber\CorsSubscriber;
use Pimcore\Bundle\StudioApiBundle\Service\OpenApiServiceInterface;
use Pimcore\Bundle\StudioApiBundle\Service\TokenServiceInterface;
use Symfony\Component\Config\FileLocator;
Expand Down Expand Up @@ -56,5 +57,8 @@ public function load(array $configs, ContainerBuilder $container): void

$definition = $container->getDefinition(OpenApiServiceInterface::class);
$definition->setArgument('$openApiScanPaths', $config['openApiScanPaths']);

$definition = $container->getDefinition(CorsSubscriber::class);
$definition->setArgument('$allowedHosts', $config['allowedHostsForCors']);
}
}
Loading

0 comments on commit 7c144f9

Please sign in to comment.