Skip to content
Open
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
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 @@ -9,6 +9,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
6 changes: 3 additions & 3 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}
],
"require": {
"php": ">= 7.1.0",
"php": ">= 8.3",
"ext-curl": "*",
"ext-json": "*",
"ext-session": "*",
Expand All @@ -21,11 +21,11 @@
"tracy/tracy": "^2.6",
"league/fractal": "~0.17",
"tomaj/nette-bootstrap-form": "^2.0",
"justinrainbow/json-schema": "^5.2"
"justinrainbow/json-schema": "^5.2",
"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"
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'
10 changes: 2 additions & 8 deletions src/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@ class Api
private $rateLimit;

/**
* @param EndpointInterface $endpoint
* @param ApiHandlerInterface|string $handler
* @param ApiAuthorizationInterface $authorization
* @param RateLimitInterface|null $rateLimit
* @param ApiHandlerInterface $handler
*/
public function __construct(
EndpointInterface $endpoint,
Expand All @@ -42,10 +39,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];
}
}
1 change: 1 addition & 0 deletions src/Authorization/CookieApiKeyAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ protected function readAuthorizationToken(): ?string
$this->errorMessage = 'API key is not set';
return null;
}

return $apiKey;
}

Expand Down
1 change: 1 addition & 0 deletions src/Authorization/HeaderApiKeyAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ protected function readAuthorizationToken(): ?string
$this->errorMessage = 'API key is not set';
return null;
}

return $apiKey;
}

Expand Down
1 change: 1 addition & 0 deletions src/Authorization/QueryApiKeyAuthentication.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ protected function readAuthorizationToken(): ?string
$this->errorMessage = 'API key is not set';
return null;
}

return $apiKey;
}

Expand Down
15 changes: 6 additions & 9 deletions src/Authorization/TokenAuthorization.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,6 @@ abstract class TokenAuthorization implements ApiAuthorizationInterface
*/
protected $ipDetector;

/**
* @param TokenRepositoryInterface $tokenRepository
* @param IpDetectorInterface $ipDetector
*/
public function __construct(TokenRepositoryInterface $tokenRepository, IpDetectorInterface $ipDetector)
{
$this->tokenRepository = $tokenRepository;
Expand Down Expand Up @@ -74,17 +70,17 @@ public function getErrorMessage(): ?string
* '127.0.0.1,127.0.02' - accessible from multiple IP, separator could be new line or space
* '127.0.0.1/32' - accessible from ip range
* null - disabled access
*
* @return boolean
*/
private function isValidIp(?string $ipRestrictions): bool
{
if ($ipRestrictions === null) {
return false;
}

if ($ipRestrictions === '*' || $ipRestrictions === '') {
return true;
}

$ip = $this->ipDetector->getRequestIp();

$ipWhiteList = str_replace([',', ' ', "\n"], '#', $ipRestrictions);
Expand All @@ -93,6 +89,7 @@ private function isValidIp(?string $ipRestrictions): bool
if ($whiteIp === $ip) {
return true;
}

if (strpos($whiteIp, '/') !== false) {
return $this->ipInRange($ip, $whiteIp);
}
Expand All @@ -106,14 +103,14 @@ private function isValidIp(?string $ipRestrictions): bool
*
* @param string $ip this ip will be verified
* @param string $range is in IP/CIDR format eg 127.0.0.1/24
* @return boolean
*/
private function ipInRange(string $ip, string $range): bool
{
list($range, $netmask) = explode('/', $range, 2);
[$range, $netmask] = explode('/', $range, 2);
$range_decimal = ip2long($range);
$ipDecimal = ip2long($ip);
$wildcard_decimal = pow(2, (32 - (int)$netmask)) - 1;
/** @phpstan-ignore-next-line */
$wildcard_decimal = 2 ** (32 - (int)$netmask) - 1;
$netmask_decimal = ~ $wildcard_decimal;
return (($ipDecimal & $netmask_decimal) === ($range_decimal & $netmask_decimal));
}
Expand Down
Loading
Loading