Skip to content

Commit

Permalink
Merge pull request #7 from FreeElephants/middleware
Browse files Browse the repository at this point in the history
Middleware
  • Loading branch information
samizdam authored May 8, 2020
2 parents 7954417 + 4f28c07 commit 8104b97
Show file tree
Hide file tree
Showing 25 changed files with 735 additions and 4 deletions.
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Data Transfer Object classes generation from swagger spec

## [0.0.6] - 2020-05-08
### Added
- Middleware: Authorization based on Policies, ErrorHandler, BodyParser
- Psr Application

## [0.0.5] - 2020-05-08
### Added
- Validation subpackage based on rakit/validation
Expand Down Expand Up @@ -39,7 +44,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- FastRoute Dispatcher generation from swagger operationIds

[Unreleased]: https://github.com/FreeElephants/json-api-php-toolkit/compare/0.0.5...HEAD
[Unreleased]: https://github.com/FreeElephants/json-api-php-toolkit/compare/0.0.6...HEAD
[0.0.6]: https://github.com/FreeElephants/json-api-php-toolkit/compare/0.0.6...0.0.6
[0.0.5]: https://github.com/FreeElephants/json-api-php-toolkit/compare/0.0.4...0.0.5
[0.0.4]: https://github.com/FreeElephants/json-api-php-toolkit/compare/0.0.3...0.0.4
[0.0.3]: https://github.com/FreeElephants/json-api-php-toolkit/compare/0.0.2...0.0.3
Expand Down
2 changes: 2 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"ext-json": "*",
"cebe/php-openapi": "^1.4",
"free-elephants/i18n": "^0.0.1",
"laminas/laminas-stratigility": "^3.2",
"neomerx/json-api": "^4.0",
"nette/php-generator": "^3.3",
"nikic/fast-route": "^1.3",
Expand All @@ -27,6 +28,7 @@
},
"require-dev": {
"doctrine/orm": "^2.7",
"helmich/phpunit-psr7-assert": "^4.1",
"nyholm/psr7": "^1.2",
"phpunit/phpunit": "^9.0"
},
Expand Down
52 changes: 52 additions & 0 deletions src/FreeElephants/JsonApiToolkit/Middleware/Auth/Authorization.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Auth;

use FreeElephants\JsonApiToolkit\Middleware\Auth\Exception\UnknownPolicyCheckResultException;
use FreeElephants\JsonApiToolkit\Psr\JsonApiResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class Authorization implements MiddlewareInterface
{
private PolicyInterface $policy;

private JsonApiResponseFactory $responseFactory;

public function __construct(PolicyInterface $policy, JsonApiResponseFactory $responseFactory)
{
$this->policy = $policy;
$this->responseFactory = $responseFactory;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$result = $this->policy->check($request);
switch ($result) {
case PolicyInterface::RESULT_ALLOW:
return $handler->handle($request);
break;
case PolicyInterface::RESULT_UNAUTHORIZED:
return $this->createUnauthorizedResponse($request);
break;
case PolicyInterface::RESULT_FORBIDDEN:
return $this->createForbiddenResponse($request);
break;

default:
throw new UnknownPolicyCheckResultException();
}
}

private function createUnauthorizedResponse(ServerRequestInterface $request): ResponseInterface
{
return $this->responseFactory->createSingleErrorResponse('Action require authentication', 401, $request);
}

private function createForbiddenResponse(ServerRequestInterface $request): ResponseInterface
{
return $this->responseFactory->createSingleErrorResponse('Action require authorization', 403, $request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Auth\Exception;

class UnknownPolicyCheckResultException extends \RuntimeException
{
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Auth;

use Psr\Http\Message\ServerRequestInterface;

interface PolicyInterface
{
public const RESULT_ALLOW = 0;
public const RESULT_UNAUTHORIZED = 401;
public const RESULT_FORBIDDEN = 403;

public function check(ServerRequestInterface $request): int;
}
19 changes: 19 additions & 0 deletions src/FreeElephants/JsonApiToolkit/Middleware/BodyParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class BodyParser implements MiddlewareInterface
{
public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
$request->getBody()->rewind();
$request = $request->withParsedBody(json_decode($request->getBody()->getContents(), true));

return $handler->handle($request);
}
}
33 changes: 33 additions & 0 deletions src/FreeElephants/JsonApiToolkit/Middleware/ErrorHandler.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware;

use DomainException;
use FreeElephants\JsonApiToolkit\Psr\JsonApiResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ErrorHandler implements MiddlewareInterface
{
private JsonApiResponseFactory $responseFactory;

public function __construct(JsonApiResponseFactory $responseFactory)
{
$this->responseFactory = $responseFactory;
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
try {
return $handler->handle($request);
} catch (DomainException $throwable) {
$httpStatus = 400;
} catch (\Throwable $throwable) {
$httpStatus = 500;
}

return $this->responseFactory->createErrorResponseFromException($throwable, $httpStatus, $request);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Factory;

use FreeElephants\JsonApiToolkit\Middleware\Auth\Authorization;
use FreeElephants\JsonApiToolkit\Psr\JsonApiResponseFactory;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;

class AuthorizationFactory implements MiddlewareFactoryInterface
{
private ContainerInterface $container;
private JsonApiResponseFactory $jsonApiResponseFactory;

public function __construct(ContainerInterface $container, JsonApiResponseFactory $jsonApiResponseFactory)
{
$this->container = $container;
$this->jsonApiResponseFactory = $jsonApiResponseFactory;
}

public function create(string $middlewareClass, array $params = []): MiddlewareInterface
{
$policy = $this->container->get(array_shift($params));

return new Authorization($policy, $this->jsonApiResponseFactory);
}

public function canCreate(string $middlewareClass): bool
{
return is_a($middlewareClass, Authorization::class, true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Factory;

use Psr\Http\Server\MiddlewareInterface;

class ChainDelegatingMiddlewareFactory implements MiddlewareFactoryInterface
{
/**
* @var MiddlewareFactoryInterface[]
*/
private array $middlewareFactoryClassMap;

public function __construct(array $middlewareFactoryChain)
{
$this->middlewareFactoryClassMap = $middlewareFactoryChain;
}

public function create(string $middlewareClass, array $params = []): MiddlewareInterface
{
foreach ($this->middlewareFactoryClassMap as $concreteFactory) {
if ($concreteFactory->canCreate($middlewareClass)) {
return $concreteFactory->create($middlewareClass, $params);
}
}

throw new \RuntimeException();
}

public function canCreate(string $middlewareClass): bool
{
foreach ($this->middlewareFactoryClassMap as $concreteFactory) {
if ($concreteFactory->canCreate($middlewareClass)) {
return true;
}
}

return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Factory;

use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;

class ContainerAwareFactory implements MiddlewareFactoryInterface
{
private ContainerInterface $container;

public function __construct(ContainerInterface $container)
{
$this->container = $container;
}

public function create(string $middlewareClass, array $params = []): MiddlewareInterface
{
return $this->container->get($middlewareClass);
}

public function canCreate(string $middlewareClass): bool
{
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Factory;

use Psr\Http\Server\MiddlewareInterface;

interface MiddlewareFactoryInterface
{
public function create(string $middlewareClass, array $params = []): MiddlewareInterface;

public function canCreate(string $middlewareClass): bool;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware\Factory;

use FreeElephants\JsonApiToolkit\Middleware\Validation\Validation;
use FreeElephants\JsonApiToolkit\Psr\JsonApiResponseFactory;
use FreeElephants\Validation\ValidatorInterface;
use Psr\Container\ContainerInterface;
use Psr\Http\Server\MiddlewareInterface;

class ValidationFactory implements MiddlewareFactoryInterface
{
private JsonApiResponseFactory $jsonApiResponseFactory;
private ValidatorInterface $validator;
private ContainerInterface $container;

public function __construct(ContainerInterface $container, JsonApiResponseFactory $jsonApiResponseFactory, ValidatorInterface $validator)
{
$this->jsonApiResponseFactory = $jsonApiResponseFactory;
$this->validator = $validator;
$this->container = $container;
}

public function create(string $middlewareClass, array $params = []): MiddlewareInterface
{
$rules = $this->container->get(array_shift($params));

return new Validation($this->jsonApiResponseFactory, $this->validator, $rules);
}

public function canCreate(string $middlewareClass): bool
{
return is_a($middlewareClass, Validation::class, true);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware;

use FreeElephants\JsonApiToolkit\Middleware\Factory\MiddlewareFactoryInterface;
use Laminas\Stratigility\MiddlewarePipe;
use Laminas\Stratigility\MiddlewarePipeInterface;
use Psr\Http\Message\ServerRequestInterface;

class MiddlewarePipeFactory
{
private MiddlewareFactoryInterface $middlewareFactory;
private array $middlewareMap;

public function __construct(MiddlewareFactoryInterface $middlewareFactory, array $middlewareMap)
{
$this->middlewareFactory = $middlewareFactory;
$this->middlewareMap = $middlewareMap;
}

public function create(ServerRequestInterface $request): MiddlewarePipeInterface
{
$pipe = new MiddlewarePipe();
foreach ($this->middlewareMap as $path => $middleware) {
foreach ($middleware as $middlewareClass => $config) {
$middleware = $this->middlewareFactory->create($middlewareClass, (array) $config);
$pipe->pipe(new RouteParamsPathMiddlewareDecorator($path, $middleware));
}
}

return $pipe;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

namespace FreeElephants\JsonApiToolkit\Middleware;

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class RouteParamsPathMiddlewareDecorator implements MiddlewareInterface
{
private MiddlewareInterface $middleware;
private string $path;
private string $pattern;

public function __construct($path, MiddlewareInterface $middleware)
{
$this->middleware = $middleware;
$this->path = $path;
$this->pattern = preg_replace('/({.*})/', '([a-zA-Z0-9_-]+)', $path);
}

public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
{
if ($this->match($request)) {
return $this->middleware->process($request, $handler);
}

return $handler->handle($request);
}

private function match(ServerRequestInterface $request): bool
{
return preg_match('#' . $this->pattern . '#', $request->getUri()->getPath()) > 0;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
use Psr\Http\Server\MiddlewareInterface;
use Psr\Http\Server\RequestHandlerInterface;

class ValidationMiddleware implements MiddlewareInterface
class Validation implements MiddlewareInterface
{
private ValidatorInterface $validator;
private JsonApiResponseFactory $jsonApiResponseFactory;
Expand Down
Loading

0 comments on commit 8104b97

Please sign in to comment.