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

Feature/acp 4074/acp 4403 invalid checkout with paypalexpress in glue does not produce helpful errors #19

Merged
Show file tree
Hide file tree
Changes from 7 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
1 change: 0 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@ jobs:
fail-fast: false
matrix:
php-version: [
'8.1',
'8.2',
]
steps:
Expand Down
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# AppKernel Module
[![Latest Stable Version](https://poser.pugx.org/spryker/app-kernel/v/stable.svg)](https://packagist.org/packages/spryker/app-kernel)
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.1-8892BF.svg)](https://php.net/)
[![Minimum PHP Version](https://img.shields.io/badge/php-%3E%3D%208.2-8892BF.svg)](https://php.net/)

Provides SyncAPI and AsyncAPI schema files and the needed code to make the Mini-Framework an App.

Expand All @@ -17,11 +17,37 @@ composer require spryker/app-kernel
config/Shared/config_default.php

```
use Spryker\Shared\AppKernel\AppConstants;
use Spryker\Shared\AppKernel\AppKernelConstants;

$config[AppConstants::APP_IDENTIFIER] = getenv('APP_IDENTIFIER') ?: 'hello-world';
$config[AppKernelConstants::APP_IDENTIFIER] = getenv('APP_IDENTIFIER') ?: 'hello-world';
$config[AppKernelConstants::OPEN_API_SCHEMA_PATH] = 'path/to/your/openApiSchema.yml';
```

### Validating Requests against the OpenAPI Schema

Low level validation can be done by using the `Spryker\Zed\AppKernel\Communication\Plugin\OpenApiSchemaValidatorPlugin` plugin. When this plugin is added to the GlueApplicationDependencyProvider all API requests against this App will be validated against the defined OpenAPI schema.

To enable this, you need to have a well-defined OpenAPI schema file, and you need to add the `OpenApiSchemaValidatorPlugin` plugin to the `getRestApplicationPlugins` method in your GlueApplicationDependencyProvider.

```php
use Spryker\Zed\AppKernel\Communication\Plugin\OpenApiSchemaValidatorPlugin;

...

protected function getRestApplicationPlugins(): array
{
return [
new OpenApiSchemaValidatorPlugin(),
];
}

...
```

Pay intention that this will be a hard validation that gets executed before any other code from your App gets executed. If the validation fails, the request will be rejected with a 400 Bad Request response with a proper message that explains what exactly is wrong in the request.

Make sure you have tests for your API.

### Testing the AppKernel

You can test the AppKernel as usual with Codeception. Before that you need to run some commands:
Expand Down
4 changes: 3 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
"description": "AppKernel module",
"license": "proprietary",
"require": {
"php": ">=8.1",
"php": ">=8.2",
"guzzlehttp/psr7": "^2.7",
"league/openapi-psr7-validator": "^0.22.0",
"spryker/app-kernel-extension": "^1.0.0",
"spryker/glue-application-extension": "^1.0.0",
"spryker/kernel": "^3.30.0",
Expand Down
22 changes: 18 additions & 4 deletions resources/api/openapi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,24 @@ info:
name: Spryker
url: 'https://spryker.com/app-composition-platform/'
email: [email protected]
title: 'Hello World'
title: 'AppKernel API'
license:
name: MIT
url: 'https://opensource.org/licenses/MIT'
servers:
- url: 'http://glue-backend.hello-world.spryker.local/'
- url: 'http://glue-backend.app-kernel.spryker.local'
description: 'Local development endpoint'
- url: 'https://api.hello-world-testing.aop.demo-spryker.com/'
- url: 'https://api.app-kernel-testing.aop.demo-spryker.com'
description: 'Testing'
- url: 'https://api.hello-world-staging.aop.demo-spryker.com/'
- url: 'https://api.app-kernel-staging.aop.demo-spryker.com'
description: 'Staging'
paths:
'/private/configure':
post:
operationId: 'configure'
summary: 'Saves or updates Hello World App configuration between Tenants and this App.'
security:
- Bearer: [ ]
parameters:
- $ref: '#/components/parameters/tenantIdentifier'
requestBody:
Expand Down Expand Up @@ -49,8 +53,11 @@ paths:
type: string
'/private/disconnect':
post:
operationId: 'disconnect'
summary: 'Disconnects this App from a Tenants Application.
Finds configuration and removes it.'
security:
- Bearer: [ ]
parameters:
- $ref: '#/components/parameters/tenantIdentifier'
responses:
Expand All @@ -69,6 +76,13 @@ paths:
schema:
type: string
components:
securitySchemes:
Bearer:
type: apiKey
in: header
name: Authorization
description: >-
Enter the token with the `Bearer: ` prefix, e.g. "Bearer abcde12345".
schemas:
ConfigurationApiRequest:
properties:
Expand Down
8 changes: 8 additions & 0 deletions src/Spryker/Glue/AppKernel/AppKernelConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace Spryker\Glue\AppKernel;

use Spryker\Glue\Kernel\AbstractBundleConfig;
use Spryker\Shared\AppKernel\AppKernelConstants;

class AppKernelConfig extends AbstractBundleConfig
{
Expand Down Expand Up @@ -52,7 +53,14 @@ class AppKernelConfig extends AbstractBundleConfig
public const RESPONSE_MESSAGE_DISCONNECT_ERROR = 'Disconnecting error.';

/**
* @api
*
* @var string
*/
public const ERROR_CODE_PAYMENT_DISCONNECTION_TENANT_IDENTIFIER_MISSING = '20000';

public function getOpenApiSchemaPath(): ?string
{
return $this->getConfig()->hasKey(AppKernelConstants::OPEN_API_SCHEMA_PATH) ? $this->getConfig()->get(AppKernelConstants::OPEN_API_SCHEMA_PATH) : null;
}
}
9 changes: 9 additions & 0 deletions src/Spryker/Glue/AppKernel/AppKernelFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,16 @@
use Spryker\Glue\AppKernel\Validator\BodyStructureValidator;
use Spryker\Glue\AppKernel\Validator\ConfigurationValidator;
use Spryker\Glue\AppKernel\Validator\HeaderValidator;
use Spryker\Glue\AppKernel\Validator\OpenApiRequestSchemaValidator;
use Spryker\Glue\AppKernel\Validator\RequestValidator;
use Spryker\Glue\AppKernel\Validator\RequestValidatorInterface;
use Spryker\Glue\Kernel\Backend\AbstractFactory;
use Symfony\Component\Validator\Validation;
use Symfony\Component\Validator\Validator\ValidatorInterface;

/**
* @method \Spryker\Glue\AppKernel\AppKernelConfig getConfig()
*/
class AppKernelFactory extends AbstractFactory
{
public function createHeaderValidator(): RequestValidatorInterface
Expand Down Expand Up @@ -109,4 +113,9 @@ public function getApiRequestDisconnectValidatorPlugins(): array
{
return $this->getProvidedDependency(AppKernelDependencyProvider::PLUGINS_REQUEST_DISCONNECT_VALIDATOR);
}

public function createOpenApiRequestSchemaValidator(): OpenApiRequestSchemaValidator
{
return new OpenApiRequestSchemaValidator($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 AppGlueRequestSchemaValidatorPlugin extends AbstractPlugin implements RequestValidatorPluginInterface
{
public function validate(GlueRequestTransfer $glueRequestTransfer): GlueRequestValidationTransfer
{
return $this->getFactory()->createOpenApiRequestSchemaValidator()->validate($glueRequestTransfer);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?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\GlueErrorTransfer;
use Generated\Shared\Transfer\GlueRequestTransfer;
use Generated\Shared\Transfer\GlueRequestValidationTransfer;
use GuzzleHttp\Psr7\Request;
use League\OpenAPIValidation\PSR7\Exception\Validation\InvalidBody;
use League\OpenAPIValidation\PSR7\ValidatorBuilder;
use Spryker\Glue\AppKernel\AppKernelConfig;
use Symfony\Component\HttpFoundation\Response;
use Throwable;

class OpenApiRequestSchemaValidator
{
public function __construct(protected AppKernelConfig $appKernelConfig)
{
}

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

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

if ($openApiSchemaPath === null || $openApiSchemaPath === '' || $openApiSchemaPath === '0') {
return $glueRequestValidationTransfer;
}

// Converting the HTTP request to PSR7 request
$psr7Request = new Request(
$glueRequestTransfer->getMethod() ?? '',
$glueRequestTransfer->getPath() ?? '',
$glueRequestTransfer->getMeta(),
$glueRequestTransfer->getContent(),
);

// Validate the request
$validator = (new ValidatorBuilder())
->fromYamlFile($openApiSchemaPath)
->getRequestValidator();

try {
$validator->validate($psr7Request);
} catch (Throwable $throwable) {
$glueErrorTransfer = new GlueErrorTransfer();
$glueErrorTransfer
->setMessage($this->getMessageFromThrowable($throwable));

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

return $glueRequestValidationTransfer;
}

return $glueRequestValidationTransfer;
}

protected function getMessageFromThrowable(Throwable $throwable): string
{
return match (get_class($throwable)) {
InvalidBody::class => $throwable->getVerboseMessage(),
default => $throwable->getMessage(),
};
}
}
10 changes: 10 additions & 0 deletions src/Spryker/Shared/AppKernel/AppKernelConstants.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,14 @@ class AppKernelConstants
* @var string
*/
public const APP_VERSION = 'APP:APP_VERSION';

/**
* Specification:
* - Path to an OpenAPI schema path that can be used for request validation.
*
* @api
*
* @var string
*/
public const OPEN_API_SCHEMA_PATH = 'APP:OPEN_API_SCHEMA_PATH';
}
Loading
Loading