Skip to content

Commit

Permalink
ACP-4589 Added AppConfigForTenantVaidator request validator, updated … (
Browse files Browse the repository at this point in the history
#21)

* ACP-4589 Added AppConfigForTenantVaidator request validator, updated schema request validator to be able to exclude URLs
  • Loading branch information
stereomon authored Dec 12, 2024
1 parent febb47d commit b816e3c
Show file tree
Hide file tree
Showing 9 changed files with 370 additions and 2 deletions.
4 changes: 4 additions & 0 deletions phpcs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@
<exclude name="SlevomatCodingStandard.Classes.DisallowConstructorPropertyPromotion.DisallowedConstructorPropertyPromotion"/>
</rule>

<rule ref="SlevomatCodingStandard.Functions.DisallowNamedArguments">
<exclude name="SlevomatCodingStandard.Functions.DisallowNamedArguments.DisallowedNamedArgument"/>
</rule>

<!-- PHP 8.0 null safe operator -->
<rule ref="SlevomatCodingStandard.ControlStructures.DisallowNullSafeObjectOperator">
<exclude name="SlevomatCodingStandard.ControlStructures.DisallowNullSafeObjectOperator"/>
Expand Down
23 changes: 23 additions & 0 deletions src/Spryker/Glue/AppKernel/AppKernelConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,27 @@ public function getOpenApiSchemaPath(): ?string
{
return $this->getConfig()->hasKey(AppKernelConstants::OPEN_API_SCHEMA_PATH) ? $this->getConfig()->get(AppKernelConstants::OPEN_API_SCHEMA_PATH) : null;
}

/**
* @api
*
* @return array<int, string>
*/
public function getOpenApiSchemaRequestValidationExcludedPaths(): array
{
return [];
}

/**
* @api
*
* @return array<int, string>
*/
public function getAppConfigRequestValidationExcludedPaths(): array
{
return [
static::CONFIGURE_ROUTE_PATH,
static::DISCONNECT_ROUTE_PATH,
];
}
}
6 changes: 6 additions & 0 deletions src/Spryker/Glue/AppKernel/AppKernelFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use Spryker\Glue\AppKernel\Dependency\Service\AppKernelToUtilEncodingServiceInterface;
use Spryker\Glue\AppKernel\Mapper\GlueRequestMapper;
use Spryker\Glue\AppKernel\Mapper\GlueRequestMapperInterface;
use Spryker\Glue\AppKernel\Validator\AppConfigForTenantValidator;
use Spryker\Glue\AppKernel\Validator\BodyStructureValidator;
use Spryker\Glue\AppKernel\Validator\ConfigurationValidator;
use Spryker\Glue\AppKernel\Validator\HeaderValidator;
Expand Down Expand Up @@ -118,4 +119,9 @@ public function createOpenApiRequestSchemaValidator(): OpenApiRequestSchemaValid
{
return new OpenApiRequestSchemaValidator($this->getConfig());
}

public function createAppConfigForTenantValidator(): AppConfigForTenantValidator
{
return new AppConfigForTenantValidator($this->getAppKernelFacade(), $this->getConfig());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace Spryker\Glue\AppKernel\Plugin\GlueApplication;

use Generated\Shared\Transfer\GlueRequestTransfer;
use Generated\Shared\Transfer\GlueRequestValidationTransfer;
use Spryker\Glue\GlueApplicationExtension\Dependency\Plugin\RequestValidatorPluginInterface;
use Spryker\Glue\Kernel\AbstractPlugin;

/**
* @method \Spryker\Glue\AppKernel\AppKernelFactory getFactory()
*/
class AppConfigForTenantValidatorPlugin extends AbstractPlugin implements RequestValidatorPluginInterface
{
public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestValidationTransfer
{
return $this->getFactory()->createAppConfigForTenantValidator()->validate($glueRequestTransfer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace Spryker\Glue\AppKernel\Validator;

use Generated\Shared\Transfer\AppConfigCriteriaTransfer;
use Generated\Shared\Transfer\GlueErrorTransfer;
use Generated\Shared\Transfer\GlueRequestTransfer;
use Generated\Shared\Transfer\GlueRequestValidationTransfer;
use Spryker\Glue\AppKernel\AppKernelConfig;
use Spryker\Glue\AppKernel\Dependency\Facade\AppKernelToAppKernelFacadeInterface;
use Spryker\Shared\Log\LoggerTrait;
use Spryker\Zed\AppKernel\AppKernelConfig as AppKernelAppKernelConfig;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class AppConfigForTenantValidator
{
use LoggerTrait;

public function __construct(protected AppKernelToAppKernelFacadeInterface $appKernelToAppKernelFacade, protected AppKernelConfig $appKernelConfig)
{
}

public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestValidationTransfer
{
$glueRequestValidationTransfer = new GlueRequestValidationTransfer();
$glueRequestValidationTransfer->setIsValid(true);

if ($this->isPathExcludedFromValidation($glueRequestTransfer)) {
return $glueRequestValidationTransfer;
}

$tenantIdentifier = $this->getTenantIdentifier($glueRequestTransfer);

$appConfigCriteriaTransfer = new AppConfigCriteriaTransfer();
$appConfigCriteriaTransfer->setTenantIdentifier($tenantIdentifier);

try {
$appConfigTransfer = $this->appKernelToAppKernelFacade->getConfig($appConfigCriteriaTransfer);
if ($appConfigTransfer->getStatus() === AppKernelAppKernelConfig::APP_STATUS_DISCONNECTED) {
$glueErrorTransfer = new GlueErrorTransfer();
$glueErrorTransfer->setMessage('Tenant is disconnected.');

$glueRequestValidationTransfer
->setIsValid(false)
->setStatus(Response::HTTP_FORBIDDEN)
->addError($glueErrorTransfer);
}
} catch (Throwable $throwable) {
$this->getLogger()->error(
$throwable->getMessage(),
$glueRequestTransfer->toArray(),
);

$glueErrorTransfer = new GlueErrorTransfer();
$glueErrorTransfer
->setMessage($throwable->getMessage());

$glueRequestValidationTransfer
->setIsValid(false)
->addError($glueErrorTransfer)
->setStatus(Response::HTTP_INTERNAL_SERVER_ERROR);

return $glueRequestValidationTransfer;
}

return $glueRequestValidationTransfer;
}

protected function isPathExcludedFromValidation(GlueRequestTransfer $glueRequestTransfer): bool
{
return in_array($glueRequestTransfer->getPath(), $this->appKernelConfig->getAppConfigRequestValidationExcludedPaths());
}

protected function getTenantIdentifier(GlueRequestTransfer $glueRequestTransfer): ?string
{
$meta = $glueRequestTransfer->getMeta();

if (!isset($meta['x-tenant-identifier'])) {
return null;
}

$tenantIdentifier = $meta['x-tenant-identifier'];

if (is_array($tenantIdentifier)) {
return $tenantIdentifier[0];
}

return $tenantIdentifier;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestV
$glueRequestValidationTransfer = new GlueRequestValidationTransfer();
$glueRequestValidationTransfer->setIsValid(true);

if ($this->isPathExcludedFromValidation($glueRequestTransfer)) {
return $glueRequestValidationTransfer;
}

$openApiSchemaPath = $this->appKernelConfig->getOpenApiSchemaPath();

if ($openApiSchemaPath === null || $openApiSchemaPath === '' || $openApiSchemaPath === '0') {
Expand Down Expand Up @@ -73,6 +77,11 @@ public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestV
return $glueRequestValidationTransfer;
}

protected function isPathExcludedFromValidation(GlueRequestTransfer $glueRequestTransfer): bool
{
return in_array($glueRequestTransfer->getPath(), $this->appKernelConfig->getOpenApiSchemaRequestValidationExcludedPaths());
}

protected function getMessageFromThrowable(Throwable $throwable): string
{
return match (get_class($throwable)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

namespace SprykerTest\Glue\AppKernel\Plugin\GlueApplication;

use Codeception\Stub;
use Codeception\Test\Unit;
use Generated\Shared\Transfer\GlueRequestTransfer;
use Ramsey\Uuid\Uuid;
use Spryker\Glue\AppKernel\AppKernelConfig;
use Spryker\Glue\AppKernel\AppKernelDependencyProvider;
use Spryker\Glue\AppKernel\AppKernelFactory;
use Spryker\Glue\AppKernel\Plugin\GlueApplication\AppConfigForTenantValidatorPlugin;
use Spryker\Glue\Kernel\Backend\Container;
use Spryker\Zed\AppKernel\AppKernelConfig as ZedAppKernelConfig;
use SprykerTest\Glue\AppKernel\AppKernelTester;
use SprykerTest\Glue\Testify\Helper\DependencyProviderHelperTrait;
use Symfony\Component\HttpFoundation\Response;

/**
* Auto-generated group annotations
*
* @group SprykerTest
* @group Glue
* @group AppKernel
* @group Plugin
* @group GlueApplication
* @group AppConfigForTenantValidatorPluginTest
* Add your own group annotations below this line
*/
class AppConfigForTenantValidatorPluginTest extends Unit
{
use DependencyProviderHelperTrait;

protected AppKernelTester $tester;

public function testGivenAPathIsExcludedFromTheValidationWhenTheValidatorIsExecutedThenTheRequestValidationIsSuccessful(): void
{
// Arrange
$tenantIdentifier = Uuid::uuid4()->toString();

$this->tester->haveAppConfigForTenant($tenantIdentifier, status: ZedAppKernelConfig::APP_STATUS_DISCONNECTED);

$configStub = Stub::make(AppKernelConfig::class, [
'getAppConfigRequestValidationExcludedPaths' => ['/excluded-endpoint'],
]);

$container = (new AppKernelDependencyProvider())->provideDependencies(new Container());

$factoryStub = Stub::make(AppKernelFactory::class, ['getConfig' => $configStub]);
$factoryStub->setContainer($container);

$appConfigForTenantValidatorPlugin = new AppConfigForTenantValidatorPlugin();
$appConfigForTenantValidatorPlugin->setFactory($factoryStub);

$glueRequestTransfer = new GlueRequestTransfer();
$glueRequestTransfer
->setMethod('POST')
->setPath('/excluded-endpoint')
->setMeta([
AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier,
'content-type' => 'application/json',
]);

// Act
$glueRequestValidationTransfer = $appConfigForTenantValidatorPlugin->validate($glueRequestTransfer);

// Assert
$this->assertTrue($glueRequestValidationTransfer->getIsValid());
}

public function testGivenAppConfigIsMarkedAsNewWhenTheValidatorIsExecutedThenTheRequestValidationIsSuccessful(): void
{
// Arrange
$tenantIdentifier = Uuid::uuid4()->toString();

$this->tester->haveAppConfigForTenant($tenantIdentifier, status: ZedAppKernelConfig::APP_STATUS_NEW);

$appConfigForTenantValidatorPlugin = new AppConfigForTenantValidatorPlugin();

$glueRequestTransfer = new GlueRequestTransfer();
$glueRequestTransfer
->setMethod('POST')
->setPath('/existing-endpoint')
->setMeta([
AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier,
'content-type' => 'application/json',
]);

// Act
$glueRequestValidationTransfer = $appConfigForTenantValidatorPlugin->validate($glueRequestTransfer);

// Assert
$this->assertTrue($glueRequestValidationTransfer->getIsValid());
}

public function testGivenAppConfigIsMarkedAsConnectedWhenTheValidatorIsExecutedThenTheRequestValidationIsSuccessful(): void
{
// Arrange
$tenantIdentifier = Uuid::uuid4()->toString();

$this->tester->haveAppConfigForTenant($tenantIdentifier, status: ZedAppKernelConfig::APP_STATUS_CONNECTED);

$appConfigForTenantValidatorPlugin = new AppConfigForTenantValidatorPlugin();

$glueRequestTransfer = new GlueRequestTransfer();
$glueRequestTransfer
->setMethod('POST')
->setPath('/existing-endpoint')
->setMeta([
AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier,
'content-type' => 'application/json',
]);

// Act
$glueRequestValidationTransfer = $appConfigForTenantValidatorPlugin->validate($glueRequestTransfer);

// Assert
$this->assertTrue($glueRequestValidationTransfer->getIsValid());
}

public function testGivenAppConfigIsMarkedAsDisconnectedWhenTheValidatorIsExecutedThenTheRequestValidationIsFailedAddAStatusCode403IsReturned(): void
{
// Arrange
$tenantIdentifier = Uuid::uuid4()->toString();

$this->tester->haveAppConfigForTenant($tenantIdentifier, status: ZedAppKernelConfig::APP_STATUS_DISCONNECTED);

$appConfigForTenantValidatorPlugin = new AppConfigForTenantValidatorPlugin();

$glueRequestTransfer = new GlueRequestTransfer();
$glueRequestTransfer
->setMethod('POST')
->setPath('/existing-endpoint')
->setMeta([
AppKernelConfig::HEADER_TENANT_IDENTIFIER => $tenantIdentifier,
'content-type' => 'application/json',
]);

// Act
$glueRequestValidationTransfer = $appConfigForTenantValidatorPlugin->validate($glueRequestTransfer);

// Assert
$this->assertFalse($glueRequestValidationTransfer->getIsValid());
$this->assertSame(Response::HTTP_FORBIDDEN, $glueRequestValidationTransfer->getStatus());
}

public function testGivenAppConfigIsMarkedAsDisconnectedWhenTheValidatorIsExecutedAndTheTenantIdentifierHeaderIsAnArrayThenTheRequestValidationIsFailedAddAStatusCode403IsReturned(): void
{
// Arrange
$tenantIdentifier = Uuid::uuid4()->toString();

$this->tester->haveAppConfigForTenant($tenantIdentifier, status: ZedAppKernelConfig::APP_STATUS_DISCONNECTED);

$appConfigForTenantValidatorPlugin = new AppConfigForTenantValidatorPlugin();

$glueRequestTransfer = new GlueRequestTransfer();
$glueRequestTransfer
->setMethod('POST')
->setPath('/existing-endpoint')
->setMeta([
AppKernelConfig::HEADER_TENANT_IDENTIFIER => [$tenantIdentifier],
'content-type' => 'application/json',
]);

// Act
$glueRequestValidationTransfer = $appConfigForTenantValidatorPlugin->validate($glueRequestTransfer);

// Assert
$this->assertFalse($glueRequestValidationTransfer->getIsValid());
$this->assertSame(Response::HTTP_FORBIDDEN, $glueRequestValidationTransfer->getStatus());
}
}
Loading

0 comments on commit b816e3c

Please sign in to comment.