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

Add MultiAuthorizator #144

Open
wants to merge 4 commits into
base: 4.0
Choose a base branch
from
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ All notable changes to this project will be documented in this file.
Updates should follow the [Keep a CHANGELOG](http://keepachangelog.com/) principles.

## [Unreleased][unreleased]
#### Added
* MultiAuthorizator - for multiple types of authorization for handler

#### Added
* CorsPreflightHandlerInterface - resolve multiple service registered handler error
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,14 @@ services:
- addApi(\Tomaj\NetteApi\EndpointIdentifier('GET', 1, 'users', 'cookie'), Tomaj\NetteApi\Authorization\CookieApiKeyAuthentication('api_key', @staticTokenRepository))
```

### Multiple authentication
You can use multiple types of authorization of API.

```neon
services:
- Tomaj\NetteApi\Authorization\MultiAuthorizator([@Tomaj\NetteApi\Authorization\BearerTokenAuthorization, @Tomaj\NetteApi\Authorization\QueryApiKeyAuthentication])
```

## Rate limit

This library provides simple interface for API rate limit. All you need to do is implement this interface like in example below:
Expand Down
61 changes: 61 additions & 0 deletions src/Authorization/MultiAuthorizator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<?php

declare(strict_types=1);

namespace Tomaj\NetteApi\Authorization;

use InvalidArgumentException;

class MultiAuthorizator implements ApiAuthorizationInterface
{
/**
* @var string|null
*/
protected $errorMessage = null;

/**
* @var array<ApiAuthorizationInterface>
*/
private $authorizators = [];

public function __construct(array $authorizators)
{
foreach ($authorizators as $authorizator) {
if (!$authorizator instanceof ApiAuthorizationInterface) {
throw new InvalidArgumentException(sprintf('First argument must contain only %s items.', ApiAuthorizationInterface::class));
}
$this->authorizators[] = $authorizator;
}
if (count($this->authorizators) === 0) {
throw new InvalidArgumentException('Set at least one Authorizator');
}
}

/**
* @return array<ApiAuthorizationInterface>
*/
public function getAuthorizators(): array
{
return $this->authorizators;
}

public function authorized(): bool
{
foreach ($this->authorizators as $authorizator) {
if ($authorizator->authorized()) {
return true;
} elseif ($authorizator->getErrorMessage() !== null) {
$this->errorMessage = $authorizator->getErrorMessage();
}
}
if ($this->errorMessage === null) {
$this->errorMessage = 'Request is invalid for all authorizators';
}
return false;
}

public function getErrorMessage(): ?string
{
return $this->errorMessage;
}
}
83 changes: 53 additions & 30 deletions src/Component/ApiConsoleControl.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
use Tomaj\NetteApi\Authorization\BearerTokenAuthorization;
use Tomaj\NetteApi\Authorization\CookieApiKeyAuthentication;
use Tomaj\NetteApi\Authorization\HeaderApiKeyAuthentication;
use Tomaj\NetteApi\Authorization\MultiAuthorizator;
use Tomaj\NetteApi\Authorization\NoAuthorization;
use Tomaj\NetteApi\Authorization\QueryApiKeyAuthentication;
use Tomaj\NetteApi\EndpointInterface;
Expand Down Expand Up @@ -87,27 +88,12 @@ protected function createComponentConsoleForm(): Form
$form->addText('api_method', 'Method');
$defaults['api_method'] = $this->endpoint->getMethod();

if ($this->authorization instanceof BearerTokenAuthorization) {
$form->addText('token', 'Token')
->setHtmlAttribute('placeholder', 'Enter token');
} elseif ($this->authorization instanceof BasicAuthentication) {
$form->addText('basic_authentication_username', 'Username')
->setHtmlAttribute('placeholder', 'Enter basic authentication username');
$form->addText('basic_authentication_password', 'Password')
->setHtmlAttribute('placeholder', 'Enter basic authentication password');
} elseif ($this->authorization instanceof QueryApiKeyAuthentication) {
$form->addText($this->authorization->getQueryParamName(), 'API key')
->setHtmlAttribute('placeholder', 'Enter API key');
} elseif ($this->authorization instanceof HeaderApiKeyAuthentication) {
$form->addText('header_api_key', 'API key')
->setHtmlAttribute('placeholder', 'Enter API key');
} elseif ($this->authorization instanceof CookieApiKeyAuthentication) {
$form->addText('cookie_api_key', 'API key')
->setHtmlAttribute('placeholder', 'Enter API key');
} elseif ($this->authorization instanceof NoAuthorization) {
$form->addText('authorization', 'Authorization')
->setDisabled(true);
$defaults['authorization'] = 'No authorization - global access';
if ($this->authorization instanceof MultiAuthorizator) {
foreach ($this->authorization->getAuthorizators() as $authorization) {
$this->addFormularItems($form, $authorization, $defaults);
}
} else {
$this->addFormularItems($form, $this->authorization, $defaults);
}

$form->addCheckbox('send_session_id', 'Send session id cookie');
Expand Down Expand Up @@ -159,15 +145,12 @@ public function formSucceeded(Form $form, ArrayHash $values): void

$additionalValues['timeout'] = $values['timeout'];

if ($this->authorization instanceof QueryApiKeyAuthentication) {
$queryParamName = $this->authorization->getQueryParamName();
$additionalValues['getFields'][$queryParamName] = $values[$queryParamName] ?? null;
} elseif ($this->authorization instanceof HeaderApiKeyAuthentication) {
$headerName = $this->authorization->getHeaderName();
$additionalValues['headers'][] = $headerName . ':' . $values['header_api_key'] ?? null;
} elseif ($this->authorization instanceof CookieApiKeyAuthentication) {
$cookieName = $this->authorization->getCookieName();
$additionalValues['cookieFields'][$cookieName] = $values['cookie_api_key'] ?? null;
if ($this->authorization instanceof MultiAuthorizator) {
foreach ($this->authorization->getAuthorizators() as $authorization) {
$this->addAdditionalValues($authorization, $values, $additionalValues);
}
} else {
$this->addAdditionalValues($this->authorization, $values, $additionalValues);
}

$consoleRequest = new ConsoleRequest($this->handler, $this->endpoint, $this->apiLink);
Expand Down Expand Up @@ -213,4 +196,44 @@ private function filterFormValues(array $values): array
}
return $values;
}

private function addFormularItems(Form $form, ApiAuthorizationInterface $authorization, &$defaults): void
{
if ($authorization instanceof BearerTokenAuthorization) {
$form->addText('token', 'Token')
->setHtmlAttribute('placeholder', 'Enter token');
} elseif ($authorization instanceof BasicAuthentication) {
$form->addText('basic_authentication_username', 'Username')
->setHtmlAttribute('placeholder', 'Enter basic authentication username');
$form->addText('basic_authentication_password', 'Password')
->setHtmlAttribute('placeholder', 'Enter basic authentication password');
} elseif ($authorization instanceof QueryApiKeyAuthentication) {
$form->addText($authorization->getQueryParamName(), 'API key')
->setHtmlAttribute('placeholder', 'Enter API key');
} elseif ($authorization instanceof HeaderApiKeyAuthentication) {
$form->addText('header_api_key', 'API key')
->setHtmlAttribute('placeholder', 'Enter API key');
} elseif ($authorization instanceof CookieApiKeyAuthentication) {
$form->addText('cookie_api_key', 'API key')
->setHtmlAttribute('placeholder', 'Enter API key');
} elseif ($authorization instanceof NoAuthorization) {
$form->addText('authorization', 'Authorization')
->setDisabled(true);
$defaults['authorization'] = 'No authorization - global access';
}
}

private function addAdditionalValues(ApiAuthorizationInterface $authorization, ArrayHash $values, &$additionalValues): void
{
if ($authorization instanceof QueryApiKeyAuthentication) {
$queryParamName = $authorization->getQueryParamName();
$additionalValues['getFields'][$queryParamName] = $values[$queryParamName] ?? null;
} elseif ($authorization instanceof HeaderApiKeyAuthentication) {
$headerName = $authorization->getHeaderName();
$additionalValues['headers'][] = $headerName . ':' . $values['header_api_key'] ?? null;
} elseif ($authorization instanceof CookieApiKeyAuthentication) {
$cookieName = $authorization->getCookieName();
$additionalValues['cookieFields'][$cookieName] = $values['cookie_api_key'] ?? null;
}
}
}
85 changes: 45 additions & 40 deletions src/Handlers/OpenApiHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@
use Symfony\Component\Yaml\Yaml;
use Tomaj\NetteApi\Api;
use Tomaj\NetteApi\ApiDecider;
use Tomaj\NetteApi\Authorization\ApiAuthorizationInterface;
use Tomaj\NetteApi\Authorization\BasicAuthentication;
use Tomaj\NetteApi\Authorization\BearerTokenAuthorization;
use Tomaj\NetteApi\Authorization\CookieApiKeyAuthentication;
use Tomaj\NetteApi\Authorization\HeaderApiKeyAuthentication;
use Tomaj\NetteApi\Authorization\MultiAuthorizator;
use Tomaj\NetteApi\Authorization\NoAuthorization;
use Tomaj\NetteApi\Authorization\QueryApiKeyAuthentication;
use Tomaj\NetteApi\Link\ApiLink;
Expand Down Expand Up @@ -106,46 +108,13 @@ public function handle(array $params): ResponseInterface

foreach ($apis as $api) {
$authorization = $api->getAuthorization();
if ($authorization instanceof BasicAuthentication) {
$securitySchemes['Basic'] = [
'type' => 'http',
'scheme' => 'basic',
];
continue;
}
if ($authorization instanceof BearerTokenAuthorization) {
$securitySchemes['Bearer'] = [
'type' => 'http',
'scheme' => 'bearer',
];
continue;
}
if ($authorization instanceof QueryApiKeyAuthentication) {
$queryParamName = $authorization->getQueryParamName();
$securitySchemes[$this->normalizeSecuritySchemeName('query', $queryParamName)] = [
'type' => 'apiKey',
'in' => 'query',
'name' => $queryParamName,
];
continue;
}
if ($authorization instanceof HeaderApiKeyAuthentication) {
$headerName = $authorization->getHeaderName();
$securitySchemes[$this->normalizeSecuritySchemeName('header', $headerName)] = [
'type' => 'apiKey',
'in' => 'header',
'name' => $headerName,
];
continue;
}
if ($authorization instanceof CookieApiKeyAuthentication) {
$cookieName = $authorization->getCookieName();
$securitySchemes[$this->normalizeSecuritySchemeName('cookie', $cookieName)] = [
'type' => 'apiKey',
'in' => 'cookie',
'name' => $cookieName,
];
continue;

if ($authorization instanceof MultiAuthorizator) {
foreach ($authorization->getAuthorizators() as $tmpAuthorization) {
$this->addSecuritySchema($tmpAuthorization, $securitySchemes);
}
} else {
$this->addSecuritySchema($authorization, $securitySchemes);
}
}

Expand Down Expand Up @@ -586,6 +555,42 @@ private function createRequestBody(ApiHandlerInterface $handler)
return null;
}

private function addSecuritySchema(ApiAuthorizationInterface $authorization, &$securitySchemes): void
{
if ($authorization instanceof BasicAuthentication) {
$securitySchemes['Basic'] = [
'type' => 'http',
'scheme' => 'basic',
];
} elseif ($authorization instanceof BearerTokenAuthorization) {
$securitySchemes['Bearer'] = [
'type' => 'http',
'scheme' => 'bearer',
];
} elseif ($authorization instanceof QueryApiKeyAuthentication) {
$queryParamName = $authorization->getQueryParamName();
$securitySchemes[$this->normalizeSecuritySchemeName('query', $queryParamName)] = [
'type' => 'apiKey',
'in' => 'query',
'name' => $queryParamName,
];
} elseif ($authorization instanceof HeaderApiKeyAuthentication) {
$headerName = $authorization->getHeaderName();
$securitySchemes[$this->normalizeSecuritySchemeName('header', $headerName)] = [
'type' => 'apiKey',
'in' => 'header',
'name' => $headerName,
];
} elseif ($authorization instanceof CookieApiKeyAuthentication) {
$cookieName = $authorization->getCookieName();
$securitySchemes[$this->normalizeSecuritySchemeName('cookie', $cookieName)] = [
'type' => 'apiKey',
'in' => 'cookie',
'name' => $cookieName,
];
}
}

private function createIn($type)
{
if ($type == InputParam::TYPE_GET) {
Expand Down
80 changes: 80 additions & 0 deletions tests/Authorization/MultiAuthorizatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<?php

declare(strict_types=1);

namespace Tomaj\NetteApi\Test\Handler;

use PHPUnit\Framework\TestCase;
use Tomaj\NetteApi\Authorization\HeaderApiKeyAuthentication;
use Tomaj\NetteApi\Authorization\MultiAuthorizator;
use Tomaj\NetteApi\Authorization\QueryApiKeyAuthentication;
use Tomaj\NetteApi\Misc\StaticTokenRepository;
use Tomaj\NetteApi\Misc\StaticIpDetector;

class MultiAuthorizatorTest extends TestCase
{
public function testAllAuthorizedApiKey()
{
$_GET['api_key'] = 'sad0f98uwegoihweg09i4hergy';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$queryAuthorization = new QueryApiKeyAuthentication('api_key', $tokenRepository, $ipDetector);

$_SERVER['HTTP_X_API_KEY'] = 'sad0f98uwegoihweg09i4hergy';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$headerAuthorization = new HeaderApiKeyAuthentication('X-API-KEY', $tokenRepository, $ipDetector);

$authorization = new MultiAuthorizator([$queryAuthorization, $headerAuthorization]);
$this->assertTrue($authorization->authorized());
}

public function testFirstAuthorizedApiKey()
{
$_GET['api_key'] = 'sad0f98uwegoihweg09i4hergy';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$queryAuthorization = new QueryApiKeyAuthentication('api_key', $tokenRepository, $ipDetector);

$_SERVER['HTTP_X_API_KEY'] = 'asflkhwetiohegedgfsdgwe';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$headerAuthorization = new HeaderApiKeyAuthentication('X-API-KEY', $tokenRepository, $ipDetector);

$authorization = new MultiAuthorizator([$queryAuthorization, $headerAuthorization]);
$this->assertTrue($authorization->authorized());
}

public function testSecondAuthorizedApiKey()
{
$_GET['api_key'] = 'asflkhwetiohegedgfsdgwe';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$queryAuthorization = new QueryApiKeyAuthentication('api_key', $tokenRepository, $ipDetector);

$_SERVER['HTTP_X_API_KEY'] = 'sad0f98uwegoihweg09i4hergy';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$headerAuthorization = new HeaderApiKeyAuthentication('X-API-KEY', $tokenRepository, $ipDetector);

$authorization = new MultiAuthorizator([$queryAuthorization, $headerAuthorization]);
$this->assertTrue($authorization->authorized());
}

public function testUnauthorizedApiKey()
{
$_GET['api_key'] = 'asflkhwetiohegedgfsdgwe';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$queryAuthorization = new QueryApiKeyAuthentication('api_key', $tokenRepository, $ipDetector);

$_SERVER['HTTP_X_API_KEY'] = 'asflkhwetiohegedgfsdgwe';
$tokenRepository = new StaticTokenRepository(['sad0f98uwegoihweg09i4hergy' => '*']);
$ipDetector = new StaticIpDetector('34.24.126.44');
$headerAuthorization = new HeaderApiKeyAuthentication('X-API-KEY', $tokenRepository, $ipDetector);

$authorization = new MultiAuthorizator([$queryAuthorization, $headerAuthorization]);
$this->assertFalse($authorization->authorized());
$this->assertEquals('Token doesn\'t exists or isn\'t active', $authorization->getErrorMessage());
}
}
Loading
Loading