API helper classes
This commit is contained in:
26
composer.json
Normal file
26
composer.json
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "provm/api",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"type": "library",
|
||||||
|
"require": {
|
||||||
|
"psr/http-message": "*",
|
||||||
|
"slim/slim": "^4.0"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"phpunit/phpunit": "*"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Aldarien",
|
||||||
|
"email": "aldarien85@gmail.com"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"ProVM\\": "src/"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"config": {
|
||||||
|
"sort-packages": true
|
||||||
|
}
|
||||||
|
}
|
17
src/Concept/API/Controller.php
Normal file
17
src/Concept/API/Controller.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Concept\API;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
interface Controller
|
||||||
|
{
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||||
|
public function get(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface;
|
||||||
|
public function getMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||||
|
public function add(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||||
|
public function edit(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface;
|
||||||
|
public function editMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||||
|
public function delete(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface;
|
||||||
|
public function deleteMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface;
|
||||||
|
}
|
13
src/Exception/Authorization/MissingBearer.php
Normal file
13
src/Exception/Authorization/MissingBearer.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Exception\Authorization;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class MissingBearer extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct('Missing Bearer token', 401, $previous);
|
||||||
|
}
|
||||||
|
}
|
13
src/Exception/Authorization/MissingHeader.php
Normal file
13
src/Exception/Authorization/MissingHeader.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Exception\Authorization;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class MissingHeader extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(Throwable $previous = null)
|
||||||
|
{
|
||||||
|
parent::__construct('No Authorization header found', 401, $previous);
|
||||||
|
}
|
||||||
|
}
|
13
src/Exception/Authorization/MissingToken.php
Normal file
13
src/Exception/Authorization/MissingToken.php
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Exception\Authorization;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class MissingToken extends Exception
|
||||||
|
{
|
||||||
|
public function __construct(string $placement, Throwable $previous)
|
||||||
|
{
|
||||||
|
parent::__construct("No token found in {$placement}", 401, $previous);
|
||||||
|
}
|
||||||
|
}
|
128
src/Ideal/API/Controller.php
Normal file
128
src/Ideal/API/Controller.php
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Ideal\API;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use ProVM\Concept;
|
||||||
|
use ProVM\Reinforce\Controller\Json;
|
||||||
|
|
||||||
|
abstract class Controller implements Concept\API\Controller
|
||||||
|
{
|
||||||
|
use Json;
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$output = [
|
||||||
|
$this->getPlural() => $this->getService()->getAll()
|
||||||
|
];
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function get(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface
|
||||||
|
{
|
||||||
|
$output = [
|
||||||
|
'id' => $id,
|
||||||
|
$this->getSingular() => $this->getService()->getById($id)
|
||||||
|
];
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function getMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$input = $request->getParsedBody();
|
||||||
|
$output = [
|
||||||
|
'input' => $input,
|
||||||
|
$this->getPlural() => []
|
||||||
|
];
|
||||||
|
if (isset($input[$this->getPlural()])) {
|
||||||
|
if (!is_array($input[$this->getPlural()])) {
|
||||||
|
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||||
|
}
|
||||||
|
foreach ($input[$this->getPlural()] as $id) {
|
||||||
|
$output[$this->getPlural()][$id] = $this->getService()->getById($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function add(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$input = $request->getParsedBody();
|
||||||
|
$output = [
|
||||||
|
'input' => $input,
|
||||||
|
$this->getPlural() => []
|
||||||
|
];
|
||||||
|
if (isset($input[$this->getPlural()])) {
|
||||||
|
if (!is_array($input[$this->getPlural()])) {
|
||||||
|
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||||
|
}
|
||||||
|
foreach ($input[$this->getPlural()] as $data) {
|
||||||
|
$output[$this->getPlural()] []= $this->getService()->add($data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function edit(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface
|
||||||
|
{
|
||||||
|
$input = $request->getParsedBody();
|
||||||
|
$obj = $this->getService()->getById($id);
|
||||||
|
$output = [
|
||||||
|
'id' => $id,
|
||||||
|
'input' => $input,
|
||||||
|
'old' => $obj,
|
||||||
|
$this->getSingular() => $this->getService()->edit($obj, $input)
|
||||||
|
];
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function editMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$input = $request->getParsedBody();
|
||||||
|
$output = [
|
||||||
|
'input' => $input,
|
||||||
|
'old' => [],
|
||||||
|
$this->getPlural() => []
|
||||||
|
];
|
||||||
|
if (isset($input[$this->getPlural()])) {
|
||||||
|
if (!is_array($input[$this->getPlural()])) {
|
||||||
|
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||||
|
}
|
||||||
|
foreach ($input[$this->getPlural()] as $id => $data) {
|
||||||
|
$obj = $this->getService()->getById($id);
|
||||||
|
$output['old'][$id]= $obj;
|
||||||
|
$output[$this->getPlural()][$id]= $this->getService()->edit($obj, $data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function delete(ServerRequestInterface $request, ResponseInterface $response, int $id): ResponseInterface
|
||||||
|
{
|
||||||
|
$obj = $this->getService()->getById($id);
|
||||||
|
$output = [
|
||||||
|
'id' => $id,
|
||||||
|
$this->getSingular() => $obj,
|
||||||
|
'deleted' => $this->getService()->delete($obj)
|
||||||
|
];
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
public function deleteMany(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
|
||||||
|
{
|
||||||
|
$input = $request->getParsedBody();
|
||||||
|
$output = [
|
||||||
|
'input' => $input,
|
||||||
|
'old' => [],
|
||||||
|
$this->getPlural() => []
|
||||||
|
];
|
||||||
|
if (isset($input[$this->getPlural()])) {
|
||||||
|
if (!is_array($input[$this->getPlural()])) {
|
||||||
|
$input[$this->getPlural()] = json_decode($input[$this->getPlural()], true);
|
||||||
|
}
|
||||||
|
foreach ($input[$this->getPlural()] as $id) {
|
||||||
|
$obj = $this->getService()->getById($id);
|
||||||
|
$output['old'][$id] = $obj;
|
||||||
|
$output[$this->getPlural()][$id] = $this->getService()->delete($obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->withJson($response, $output);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract protected function getService(): Concept\Model\Service;
|
||||||
|
abstract protected function getSingular(): string;
|
||||||
|
abstract protected function getPlural(): string;
|
||||||
|
}
|
30
src/Middleware/Authorization.php
Normal file
30
src/Middleware/Authorization.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Middleware;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseFactoryInterface;
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
use Psr\Http\Server\RequestHandlerInterface;
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use ProVM\Exception\Authorization\MissingToken;
|
||||||
|
use ProVM\Service;
|
||||||
|
|
||||||
|
class Authorization
|
||||||
|
{
|
||||||
|
public function __construct(protected ResponseFactoryInterface $responseFactory,
|
||||||
|
protected Service\Authorization $authorizationService,
|
||||||
|
protected LoggerInterface $logger) {}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (!$this->authorizationService->isAuthorized($request)) {
|
||||||
|
return $this->responseFactory->createResponse(403); // Forbidden
|
||||||
|
}
|
||||||
|
} catch (MissingToken $exception) {
|
||||||
|
$this->logger->alert($exception, ['request' => $request]);
|
||||||
|
return $this->responseFactory->createResponse(401); // Unathorized
|
||||||
|
}
|
||||||
|
return $handler->handle($request);
|
||||||
|
}
|
||||||
|
}
|
25
src/Middleware/NotFound.php
Normal file
25
src/Middleware/NotFound.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Middleware;
|
||||||
|
|
||||||
|
use Psr\Http\Message\{ResponseInterface, ServerRequestInterface, ResponseFactoryInterface};
|
||||||
|
use Psr\Http\Server\{RequestHandlerInterface};
|
||||||
|
use Psr\Log\LoggerInterface;
|
||||||
|
use Slim\Exception\HttpNotFoundException;
|
||||||
|
|
||||||
|
class NotFound
|
||||||
|
{
|
||||||
|
public function __construct(protected ResponseFactoryInterface $responseFactory, protected LoggerInterface $logger) {}
|
||||||
|
|
||||||
|
public function __invoke(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $handler->handle($request);
|
||||||
|
} catch (HttpNotFoundException $exception) {
|
||||||
|
$this->logger->error($exception);
|
||||||
|
$response = $this->responseFactory->createResponse(404)
|
||||||
|
->withHeader('Content-Type', 'application/json');
|
||||||
|
$response->getBody()->write(json_encode(['code' => 404, 'error' => 'Not Found']));
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
src/Reinforce/Controller/Json.php
Normal file
15
src/Reinforce/Controller/Json.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Reinforce\Controller;
|
||||||
|
|
||||||
|
use Psr\Http\Message\ResponseInterface;
|
||||||
|
|
||||||
|
trait Json
|
||||||
|
{
|
||||||
|
public function withJson(ResponseInterface $response, mixed $data = null, int $statusCode = 200): ResponseInterface
|
||||||
|
{
|
||||||
|
$response->getBody()->write(json_encode($data));
|
||||||
|
return $response
|
||||||
|
->withStatus($statusCode)
|
||||||
|
->withHeader('Content-Type', 'application/json');
|
||||||
|
}
|
||||||
|
}
|
85
src/Service/Authorization.php
Normal file
85
src/Service/Authorization.php
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
<?php
|
||||||
|
namespace ProVM\Service;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use ProVM\Exception\Authorization\MissingBearer;
|
||||||
|
use ProVM\Exception\Authorization\MissingHeader;
|
||||||
|
use ProVM\Exception\Authorization\MissingToken;
|
||||||
|
use Psr\Http\Message\ServerRequestInterface;
|
||||||
|
|
||||||
|
class Authorization
|
||||||
|
{
|
||||||
|
public function __construct(protected string $token) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MissingToken
|
||||||
|
*/
|
||||||
|
public function isAuthorized(ServerRequestInterface $request): bool
|
||||||
|
{
|
||||||
|
$suppliedToken = $this->getToken($request);
|
||||||
|
return ($suppliedToken === $this->token);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MissingToken
|
||||||
|
*/
|
||||||
|
private function getToken(ServerRequestInterface $request): string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->getHeaderToken($request);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
try {
|
||||||
|
return $this->getBodyToken($request, $exception);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
try {
|
||||||
|
return $this->getQueryToken($request, $exception);
|
||||||
|
} catch (Exception $exception) {
|
||||||
|
throw new MissingToken('request', $exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MissingHeader
|
||||||
|
* @throws MissingBearer
|
||||||
|
*/
|
||||||
|
private function getHeaderToken(ServerRequestInterface $request): string
|
||||||
|
{
|
||||||
|
$headers = $request->getHeader('Authorization');
|
||||||
|
if (empty($headers)) {
|
||||||
|
throw new MissingHeader();
|
||||||
|
}
|
||||||
|
foreach ($headers as $header) {
|
||||||
|
if (!str_starts_with($header, 'Bearer ')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return substr($header, strlen('Bearer '));
|
||||||
|
}
|
||||||
|
throw new MissingBearer();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MissingToken
|
||||||
|
*/
|
||||||
|
private function getBodyToken(ServerRequestInterface $request, Exception $previous): string
|
||||||
|
{
|
||||||
|
$body = $request->getParsedBody();
|
||||||
|
if (empty($body['token'])) {
|
||||||
|
throw new MissingToken('body', $previous);
|
||||||
|
}
|
||||||
|
return $body['token'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @throws MissingToken
|
||||||
|
*/
|
||||||
|
private function getQueryToken(ServerRequestInterface $request, Exception $previous): string
|
||||||
|
{
|
||||||
|
$query = $request->getQueryParams();
|
||||||
|
if (empty($query['token'])) {
|
||||||
|
throw new MissingToken('query', $previous);
|
||||||
|
}
|
||||||
|
return $query['token'];
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user