diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..463ba7e --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,32 @@ +name: Integrity check + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@master + + - name: Install PHP + uses: shivammathur/setup-php@master + with: + php-version: 7.4 + + - name: Install composer deps + run: | + composer create-project nette/code-checker temp/code-checker ^3 --no-progress + composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress + + # Install app deps + composer install --no-interaction --prefer-dist + + # Check code checker and coding standards + - name: Check coding standards + run: | + php temp/code-checker/code-checker --short-arrays --strict-types --fix --no-progress + php temp/coding-standard/ecs check src --config temp/coding-standard/coding-standard-php71.yml + + - name: Check PHPStan rules + run: composer phpstan diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..875fb4b --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Baraja packages + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..d808262 --- /dev/null +++ b/README.md @@ -0,0 +1,50 @@ +Structured API token authorizator +================================= + +![Integrity check](https://github.com/baraja-core/structured-api-token-authorizator/workflows/Integrity%20check/badge.svg) + +A simple token authorizer for authenticating HTTP requests. + +This package is the official extension for the [Baraja Structured API](https://github.com/baraja-core/structured-api). + +Simple usage +------------ + +Install this package using Composer and register the DIC extension (if you use [Baraja Package manager](https://github.com/baraja-core/package-manager), it will be registered automatically). + +Extension definition for manual usage: + +```yaml +extensions: + tokenAuthorizator: Baraja\TokenAuthorizator\TokenAuthorizatorExtension +``` + +The package automatically disables the default system method of authenticating requests through Nette User and will require token authentication. + +A token is any valid string in the query parameter `token`, or in BODY (in the case of a POST request). The token evaluates as an endpoint call parameter and can be passed to the target endpoint as a string. + +Request verification +-------------------- + +If you are not using your own token authentication implementation, the default `SimpleStrategy` will be used, which you can configure the token via NEON configuration. + +If you do not set a token, all requests (even without a token) will be considered valid. + +Simple configuration example: + +```yaml +tokenAuthorizator: + token: abcd +``` + +This configuration accepts requests as: `/api/v1/user?token=abcd`. + +Custom authentication +--------------------- + +If you need more complex authentication logic, implement a service that implements the `VerificationStrategy` interface and register it with the DIC. This service will be called automatically when all requests are verified. + +📄 License +----------- + +`baraja-core/structured-api-token-authorizator` is licensed under the MIT license. See the [LICENSE](https://github.com/baraja-core/structured-api-token-authorizator/blob/master/LICENSE) file for more details. diff --git a/common.neon b/common.neon new file mode 100644 index 0000000..7e00342 --- /dev/null +++ b/common.neon @@ -0,0 +1,2 @@ +extensions: + tokenAuthorizator: Baraja\TokenAuthorizator\TokenAuthorizatorExtension diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..59fac6a --- /dev/null +++ b/composer.json @@ -0,0 +1,32 @@ +{ + "name": "baraja-core/structured-api-token-authorizator", + "description": "A simple token authorizer for authenticating HTTP requests.", + "homepage": "https://github.com/baraja-core/structured-api-token-authorizator", + "authors": [ + { + "name": "Jan Barášek", + "homepage": "https://baraja.cz" + } + ], + "require": { + "php": ">=7.4.0", + "baraja-core/structured-api": "^2.4" + }, + "require-dev": { + "phpstan/phpstan": "^0.12.18", + "tracy/tracy": "^2.7", + "phpstan/phpstan-nette": "^0.12.6", + "symplify/easy-coding-standard": "^7.2" + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "scripts": { + "phpstan": [ + "vendor/bin/phpstan analyse src -c phpstan.neon --level 8 --no-progress" + ] + }, + "minimum-stability": "stable" +} diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..25bf1ec --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,3 @@ +includes: + - vendor/phpstan/phpstan-nette/extension.neon + - vendor/phpstan/phpstan-nette/rules.neon diff --git a/src/SimpleStrategy.php b/src/SimpleStrategy.php new file mode 100644 index 0000000..bc5b088 --- /dev/null +++ b/src/SimpleStrategy.php @@ -0,0 +1,29 @@ +token = $token; + } + + + public function verify(string $token): bool + { + return $token === $this->token; + } + + + public function isActive(): bool + { + return $this->token !== null; + } +} diff --git a/src/TokenAuthorizator.php b/src/TokenAuthorizator.php new file mode 100644 index 0000000..124557c --- /dev/null +++ b/src/TokenAuthorizator.php @@ -0,0 +1,62 @@ +strategy = $strategy ?? new SimpleStrategy($secret); + } + + + public function setStrategy(VerificationStrategy $strategy): void + { + $this->strategy = $strategy; + } + + + /** + * @param mixed[] $params + */ + public function beforeProcess(Endpoint $endpoint, array $params, string $action, string $method): ?Response + { + if ($this->strategy->isActive() === false) { + return null; + } + try { + $docComment = trim((string) (new \ReflectionClass($endpoint))->getDocComment()); + if (preg_match('/@public(?:$|\s|\n)/', $docComment)) { + return null; + } + } catch (\ReflectionException $e) { + throw new \InvalidArgumentException('Endpoint "' . \get_class($endpoint) . '" can not be reflected: ' . $e->getMessage(), $e->getCode(), $e); + } + if (isset($params['token']) === false) { + throw new \InvalidArgumentException('Parameter "token" is required.'); + } + if ($this->strategy->verify($params['token'])) { + return null; + } + throw new \InvalidArgumentException('Token is invalid or expired, please contact your administrator.'); + } + + + /** + * @param mixed[] $params + */ + public function afterProcess(Endpoint $endpoint, array $params, ?Response $response): ?Response + { + return null; + } +} diff --git a/src/TokenAuthorizatorExtension.php b/src/TokenAuthorizatorExtension.php new file mode 100644 index 0000000..e2e64bb --- /dev/null +++ b/src/TokenAuthorizatorExtension.php @@ -0,0 +1,56 @@ + Expect::string(), + ])->castTo('array'); + } + + + public function beforeCompile(): void + { + /** @var mixed[] $config */ + $config = $this->getConfig(); + $builder = $this->getContainerBuilder(); + + /** @var ServiceDefinition $apiManager */ + $apiManager = $builder->getDefinitionByType(ApiManager::class); + + /** @var ServiceDefinition $convention */ + $convention = $builder->getDefinitionByType(Convention::class); + + $convention->addSetup('?->setIgnoreDefaultPermission(true)', ['@self']); + + $builder->addDefinition($this->prefix('tokenAuthorizator')) + ->setFactory(TokenAuthorizator::class) + ->setAutowired(TokenAuthorizator::class) + ->setArgument('secret', $config['token'] ?? null); + + $apiManager->addSetup('?->addMatchExtension(?)', ['@self', '@' . TokenAuthorizator::class]); + } +} diff --git a/src/VerificationStrategy.php b/src/VerificationStrategy.php new file mode 100644 index 0000000..31285ca --- /dev/null +++ b/src/VerificationStrategy.php @@ -0,0 +1,13 @@ +