Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
32 changes: 32 additions & 0 deletions .github/workflows/phpstan.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: PHPstan 8

on:
push:
branches:
- master
pull_request: null

jobs:
phpunit:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: [ '8.3', '8.4' ]

name: PHPstan PHP ${{ matrix.php }}

steps:
- name: Checkout code
uses: actions/checkout@v2

- name: Setup PHP
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}

- name: Composer update
run: composer update --no-progress --no-interaction

- name: PHPstan
run: vendor/bin/phpstan --level=8
2 changes: 1 addition & 1 deletion .github/workflows/phpunit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '8.2', '8.3', '8.4' ]
php: [ '8.3', '8.4' ]

name: PHPunit PHP ${{ matrix.php }}

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/syntax_checker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
fail-fast: false
matrix:
php: [ '8.2', '8.3', '8.4' ]
php: [ '8.3', '8.4' ]

name: PHP syntax checker PHP ${{ matrix.php }}

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ vendor
build
coverage.clover
.idea
.phpunit.result.cache
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) princip
### Changed
* Update Request body schema in OpenApiHandler
* Set null value when process empty file params, instead of false
* [BC] Update to php >= 8.3, support for 8.4
* [BC] addApi register method now requires ApiHandlerInterface as second parameter instead of string or service name

## 3.0.0

Expand Down
9 changes: 5 additions & 4 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}
],
"require": {
"php": ">= 8.2.0",
"php": ">= 8.3",
"ext-curl": "*",
"ext-json": "*",
"ext-session": "*",
Expand All @@ -21,14 +21,15 @@
"tracy/tracy": "^2.6",
"league/fractal": "~0.20",
"tomaj/nette-bootstrap-form": "^2.0",
"justinrainbow/json-schema": "^5.2|^6.6"
"justinrainbow/json-schema": "^5.2|^6.6",
"latte/latte": "^3.0"
},
"require-dev": {
"nette/di": "^3.0",
"latte/latte": "^2.4 | ^3.0",
"phpunit/phpunit": ">7.0 <10.0",
"symfony/yaml": "^4.4|^5.0|^6.0",
"squizlabs/php_codesniffer": "^3.2"
"squizlabs/php_codesniffer": "^3.2",
"phpstan/phpstan": "^2.1"
},
"autoload": {
"psr-4": {
Expand Down
12 changes: 12 additions & 0 deletions phpstan.neon
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
parameters:
level: 8
paths:
- src
excludePaths:
- vendor
- tests
ignoreErrors:
# Ignore magic number warnings in Nette SmartObject trait (vendor code)
-
message: '#^Do not use magic number in bitwise operations\. Move to constant with a suitable name\.$#'
path: 'vendor/nette/utils/src/SmartObject.php'
28 changes: 5 additions & 23 deletions src/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,14 @@

class Api
{
private $endpoint;
private RateLimitInterface $rateLimit;

private $handler;

private $authorization;

private $rateLimit;

/**
* @param EndpointInterface $endpoint
* @param ApiHandlerInterface|string $handler
* @param ApiAuthorizationInterface $authorization
* @param RateLimitInterface|null $rateLimit
*/
public function __construct(
EndpointInterface $endpoint,
$handler,
ApiAuthorizationInterface $authorization,
private EndpointInterface $endpoint,
private ApiHandlerInterface $handler,
private ApiAuthorizationInterface $authorization,
?RateLimitInterface $rateLimit = null
) {
$this->endpoint = $endpoint;
$this->handler = $handler;
$this->authorization = $authorization;
$this->rateLimit = $rateLimit ?: new NoRateLimit();
}

Expand All @@ -42,10 +27,7 @@ public function getEndpoint(): EndpointInterface
return $this->endpoint;
}

/**
* @return ApiHandlerInterface|string
*/
public function getHandler()
public function getHandler(): ApiHandlerInterface
{
return $this->handler;
}
Expand Down
42 changes: 9 additions & 33 deletions src/ApiDecider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,39 +4,26 @@

namespace Tomaj\NetteApi;

use Nette\DI\Container;
use Nette\Http\Response;
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
use Tomaj\NetteApi\Authorization\NoAuthorization;
use Tomaj\NetteApi\Handlers\ApiHandlerInterface;
use Tomaj\NetteApi\Handlers\CorsPreflightHandler;
use Tomaj\NetteApi\Handlers\CorsPreflightHandlerInterface;
use Tomaj\NetteApi\Handlers\DefaultHandler;
use Tomaj\NetteApi\RateLimit\RateLimitInterface;
use Tomaj\NetteApi\Handlers\CorsPreflightHandlerInterface;

class ApiDecider
{
/** @var Container */
private $container;

/** @var Api[] */
private $apis = [];

/** @var ApiHandlerInterface|null */
private $globalPreflightHandler = null;

public function __construct(Container $container)
{
$this->container = $container;
}
private ?ApiHandlerInterface $globalPreflightHandler = null;

/**
* Get api handler that match input method, version, package and apiAction.
* If decider cannot find handler for given handler, returns defaults.
*
* @param string $method
* @param string $version
* @param string $package
* @param string $apiAction
*
* @return Api
Expand All @@ -54,31 +41,28 @@ public function getApi(string $method, string $version, string $package, ?string
$handler->setEndpointIdentifier($endpointIdentifier);
return new Api($api->getEndpoint(), $handler, $api->getAuthorization(), $api->getRateLimit());
}

if ($method === 'OPTIONS' && $this->globalPreflightHandler && $identifier->getVersion() === $version && $identifier->getPackage() === $package && $identifier->getApiAction() === $apiAction) {
return new Api(new EndpointIdentifier('OPTIONS', $version, $package, $apiAction), $this->globalPreflightHandler, new NoAuthorization());
}
}

return new Api(new EndpointIdentifier($method, $version, $package, $apiAction), new DefaultHandler(), new NoAuthorization());
}

public function enableGlobalPreflight(CorsPreflightHandlerInterface $corsHandler = null)
public function enableGlobalPreflight(?CorsPreflightHandlerInterface $corsHandler = null): void
{
if (!$corsHandler) {
$corsHandler = new CorsPreflightHandler(new Response());
}

$this->globalPreflightHandler = $corsHandler;
}

/**
* Register new api handler
*
* @param EndpointInterface $endpointIdentifier
* @param ApiHandlerInterface|string $handler
* @param ApiAuthorizationInterface $apiAuthorization
* @param RateLimitInterface|null $rateLimit
* @return self
*/
public function addApi(EndpointInterface $endpointIdentifier, $handler, ApiAuthorizationInterface $apiAuthorization, RateLimitInterface $rateLimit = null): self
public function addApi(EndpointInterface $endpointIdentifier, ApiHandlerInterface $handler, ApiAuthorizationInterface $apiAuthorization, ?RateLimitInterface $rateLimit = null): self
{
$this->apis[] = new Api($endpointIdentifier, $handler, $apiAuthorization, $rateLimit);
return $this;
Expand All @@ -96,20 +80,12 @@ public function getApis(): array
$handler = $this->getHandler($api);
$apis[] = new Api($api->getEndpoint(), $handler, $api->getAuthorization(), $api->getRateLimit());
}

return $apis;
}

private function getHandler(Api $api): ApiHandlerInterface
{
$handler = $api->getHandler();
if (!is_string($handler)) {
return $handler;
}

if (str_starts_with($handler, '@')) {
return $this->container->getByName(substr($handler, 1));
}

return $this->container->getByType($handler);
return $api->getHandler();
}
}
4 changes: 0 additions & 4 deletions src/Authorization/ApiAuthorizationInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,12 @@ interface ApiAuthorizationInterface
{
/**
* Main method to check if this authorization authorize actual request.
*
* @return boolean
*/
public function authorized(): bool;

/**
* If authorization deny acces, this method should provide additional information
* abount cause of restriction.
*
* @return string|null
*/
public function getErrorMessage(): ?string;
}
6 changes: 3 additions & 3 deletions src/Authorization/BasicAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,14 @@

class BasicAuthentication implements ApiAuthorizationInterface
{
/** @var array */
private $authentications;
/** @var array<string, string> */
private array $authentications;

/** @var IRequest */
private $httpRequest;

/**
* @param array<string, string> $autentications - available username - password pairs
* @param IRequest $httpRequest
*/
public function __construct(array $autentications, IRequest $httpRequest)
{
Expand All @@ -34,6 +33,7 @@ public function authorized(): bool
if (!$authentication) {
return false;
}

return $authentication === $urlScript->getPassword();
}

Expand Down
14 changes: 7 additions & 7 deletions src/Authorization/BearerTokenAuthorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,10 @@

class BearerTokenAuthorization extends TokenAuthorization
{
/**
private const EXPECTED_HTTP_PARTS = 2;

/**
* BearerTokenAuthorization constructor.
*
* @param TokenRepositoryInterface $tokenRepository
* @param IpDetectorInterface $ipDetector
*/
public function __construct(TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
{
Expand All @@ -23,24 +22,25 @@ public function __construct(TokenRepositoryInterface $tokenRepository, IpDetecto
/**
* Read HTTP reader with authorization token
* If everything is ok, it return token. In other situations returns false and set errorMessage.
*
* @return string|null
*/
protected function readAuthorizationToken(): ?string
{
if (!isset($_SERVER['HTTP_AUTHORIZATION'])) {
$this->errorMessage = 'Authorization header HTTP_Authorization is not set';
return null;
}

$parts = explode(' ', $_SERVER['HTTP_AUTHORIZATION']);
if (count($parts) !== 2) {
if (count($parts) !== self::EXPECTED_HTTP_PARTS) {
$this->errorMessage = 'Authorization header contains invalid structure';
return null;
}

if (strtolower($parts[0]) !== 'bearer') {
$this->errorMessage = 'Authorization header doesn\'t contain bearer token';
return null;
}

return $parts[1];
}
}
6 changes: 2 additions & 4 deletions src/Authorization/CookieApiKeyAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@

class CookieApiKeyAuthentication extends TokenAuthorization
{
private $cookieName;

public function __construct(string $cookieName, TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
public function __construct(private string $cookieName, TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
{
parent::__construct($tokenRepository, $ipDetector);
$this->cookieName = $cookieName;
}

protected function readAuthorizationToken(): ?string
Expand All @@ -24,6 +21,7 @@ protected function readAuthorizationToken(): ?string
$this->errorMessage = 'API key is not set';
return null;
}

return $apiKey;
}

Expand Down
6 changes: 2 additions & 4 deletions src/Authorization/HeaderApiKeyAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,9 @@

class HeaderApiKeyAuthentication extends TokenAuthorization
{
private $headerName;

public function __construct(string $headerName, TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
public function __construct(private string $headerName, TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
{
parent::__construct($tokenRepository, $ipDetector);
$this->headerName = $headerName;
}

protected function readAuthorizationToken(): ?string
Expand All @@ -25,6 +22,7 @@ protected function readAuthorizationToken(): ?string
$this->errorMessage = 'API key is not set';
return null;
}

return $apiKey;
}

Expand Down
Loading