Skip to content

Commit 35f82f8

Browse files
committed
Issue #146: Added mandatory token verification to error reporting.
1 parent ceb92d1 commit 35f82f8

12 files changed

+330
-43
lines changed

config/autoload/cli.global.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
use Api\Admin\Command\AdminCreateCommand;
66
use Api\App\Command\RouteListCommand;
7+
use Api\App\Command\TokenGenerateCommand;
78
use Dot\Cli\Command\DemoCommand;
89
use Dot\Cli\FileLockerInterface;
910

@@ -17,7 +18,8 @@
1718
'commands' => [
1819
DemoCommand::getDefaultName() => DemoCommand::class,
1920
RouteListCommand::getDefaultName() => RouteListCommand::class,
20-
AdminCreateCommand::getDefaultName() => AdminCreateCommand::class
21+
AdminCreateCommand::getDefaultName() => AdminCreateCommand::class,
22+
TokenGenerateCommand::getDefaultName() => TokenGenerateCommand::class,
2123
]
2224
],
2325
FileLockerInterface::class => [

config/autoload/error-handling.global.php

+11-3
Original file line numberDiff line numberDiff line change
@@ -40,13 +40,14 @@
4040

4141
/**
4242
* Messages will be stored only if all the below conditions are met:
43-
* enabled = true
44-
* at least one of the domain_whitelist OR ip_whitelist checks passes
43+
* - enabled = true
44+
* - request headers contain a valid error reporting token
45+
* - at least one of the domain_whitelist OR ip_whitelist checks passes
4546
*/
4647
ErrorReportServiceInterface::class => [
4748
/**
4849
* Usage:
49-
* If enabled is set to true, messages will be stored and an info message is returned.
50+
* If enabled is set to true, further checks are performed and if all good, message is stored.
5051
* If enabled is set to false, no message is stored and an error message is returned.
5152
*/
5253
'enabled' => true,
@@ -57,6 +58,13 @@
5758
*/
5859
'path' => __DIR__ . '/../../log/error-report-endpoint-log.log',
5960

61+
/**
62+
* In order to be eligible for storing messages, Requests sent to the error reporting endpoint, must contain a header having:
63+
* - name: the value of \Api\App\Service\ErrorReportService::HEADER_NAME
64+
* - value: one of the items in this array
65+
*/
66+
'tokens' => [],
67+
6068
/**
6169
* Usage:
6270
* 1. Missing/empty domain_whitelist => no domain is allowed to store messages.

documentation/DotKernel_API.postman_collection.json

+10-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
{
22
"info": {
3-
"_postman_id": "cf252f36-e656-4d44-bafd-bddc3305b866",
3+
"_postman_id": "5f4c1b92-b1e1-4f8e-840f-f92b6f4cb670",
44
"name": "DotKernel_API",
55
"description": "DotKernel API documentation.",
6-
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
6+
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json",
7+
"_exporter_id": "3494496"
78
},
89
"item": [
910
{
@@ -1292,7 +1293,13 @@
12921293
"type": "noauth"
12931294
},
12941295
"method": "POST",
1295-
"header": [],
1296+
"header": [
1297+
{
1298+
"key": "Error-Reporting-Token",
1299+
"value": "",
1300+
"type": "text"
1301+
}
1302+
],
12961303
"body": {
12971304
"mode": "raw",
12981305
"raw": "{\r\n \"message\": \"{{$randomLoremSentence}}\"\r\n}",
+55
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# Generating tokens in DotKernel API
2+
3+
This is a multipurpose command that allows creating tokens required by different parts of the API.
4+
5+
6+
## Usage
7+
8+
Go to your application's root directory.
9+
10+
Run the token generator command by executing the following command:
11+
12+
php ./bin/cli.php token:generate <type>
13+
14+
Where `<type>` is one of the following:
15+
* [error-reporting](#generate-error-reporting-token)
16+
17+
If you need help using the command, execute the following command:
18+
19+
php ./bin/cli.php token:generate --help
20+
21+
22+
### Generate error reporting token
23+
24+
You can generate an error reporting token by executing the following command:
25+
26+
php ./bin/cli.php token:generate error-reporting
27+
28+
The output should look similar to this:
29+
30+
Error reporting token:
31+
32+
0123456789abcdef0123456789abcdef01234567
33+
34+
Copy the generated token.
35+
36+
Open `config/autoload/error-handling.global.php` and paste the copied token as shown below:
37+
38+
return [
39+
...
40+
ErrorReportServiceInterface::class => [
41+
...
42+
'tokens' => [
43+
'0123456789abcdef0123456789abcdef01234567',
44+
],
45+
...
46+
]
47+
]
48+
49+
Save and close `config/autoload/error-handling.global.php`.
50+
51+
**Note**:
52+
53+
If your application is NOT in development mode, make sure you clear your config cache by executing:
54+
55+
php ./bin/clear-config-cache.php
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Command;
6+
7+
use Api\App\Service\ErrorReportServiceInterface;
8+
use Exception;
9+
use Symfony\Component\Console\Command\Command;
10+
use Symfony\Component\Console\Input\InputArgument;
11+
use Symfony\Component\Console\Input\InputInterface;
12+
use Symfony\Component\Console\Output\OutputInterface;
13+
14+
/**
15+
* Class TokenGenerateCommand
16+
* @package Api\App\Command
17+
*/
18+
class TokenGenerateCommand extends Command
19+
{
20+
protected static $defaultName = 'token:generate';
21+
private string $typeErrorReporting = 'error-reporting';
22+
private ErrorReportServiceInterface $errorReportService;
23+
24+
/**
25+
* TokenGenerateCommand constructor.
26+
*/
27+
public function __construct(ErrorReportServiceInterface $errorReportService)
28+
{
29+
parent::__construct(self::$defaultName);
30+
$this->errorReportService = $errorReportService;
31+
}
32+
33+
/**
34+
* @return void
35+
*/
36+
protected function configure(): void
37+
{
38+
$this
39+
->setName(self::$defaultName)
40+
->setDescription('Generic token generator.')
41+
->addArgument('type', InputArgument::REQUIRED, 'The type of token to be generated.')
42+
->addUsage($this->typeErrorReporting)
43+
->setHelp(<<<MSG
44+
<info>%command.name%</info> is a multipurpose command that allows creating tokens required by different parts of the API.
45+
46+
Usage:
47+
1. Create token for the error reporting endpoint:
48+
* run: <info>%command.full_name% {$this->typeErrorReporting}</info>
49+
* copy the generated token
50+
* open <comment>config/autoload/error-handling.global.php</comment>
51+
* paste the copied string inside the <comment>tokens</comment> array found under the <comment>ErrorReportServiceInterface::class</comment> key.
52+
MSG
53+
);
54+
}
55+
56+
/**
57+
* @param InputInterface $input
58+
* @param OutputInterface $output
59+
* @return int
60+
* @throws Exception
61+
*/
62+
protected function execute(InputInterface $input, OutputInterface $output): int
63+
{
64+
$type = $input->getArgument('type');
65+
match($type) {
66+
$this->typeErrorReporting => $this->generateErrorReportingToken($output),
67+
default => throw new Exception(
68+
sprintf('Unknown token type: %s', $type)
69+
)
70+
};
71+
72+
return Command::SUCCESS;
73+
}
74+
75+
private function generateErrorReportingToken(OutputInterface $output): void
76+
{
77+
$token = $this->errorReportService->generateToken();
78+
79+
$output->writeln(<<<MSG
80+
Error reporting token:
81+
82+
<info>{$token}</info>
83+
84+
* copy the generated token
85+
* open <comment>config/autoload/error-handling.global.php</comment>
86+
* paste the copied string inside the <comment>tokens</comment> array found under the <comment>ErrorReportServiceInterface::class</comment> key.
87+
MSG
88+
);
89+
}
90+
}

src/App/src/ConfigProvider.php

+3
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,11 @@
55
namespace Api\App;
66

77
use Api\App\Command\RouteListCommand;
8+
use Api\App\Command\TokenGenerateCommand;
89
use Api\App\Factory\AnnotationsCacheFactory;
910
use Api\App\Factory\AuthenticationMiddlewareFactory;
1011
use Api\App\Factory\RouteListCommandFactory;
12+
use Api\App\Factory\TokenGenerateCommandFactory;
1113
use Api\App\Middleware\AuthenticationMiddleware;
1214
use Api\App\Middleware\AuthorizationMiddleware;
1315
use Api\App\Middleware\ErrorResponseMiddleware;
@@ -77,6 +79,7 @@ public function getDependencies(): array
7779
TwigRenderer::class => TwigRendererFactory::class,
7880
ErrorResponseMiddleware::class => AnnotatedServiceFactory::class,
7981
RouteListCommand::class => RouteListCommandFactory::class,
82+
TokenGenerateCommand::class => TokenGenerateCommandFactory::class,
8083
ErrorReportService::class => AnnotatedServiceFactory::class,
8184
],
8285
'aliases' => [
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Exception;
6+
7+
use Exception;
8+
use Throwable;
9+
10+
class ForbiddenException extends Exception
11+
{
12+
public function __construct(string $message = "", int $code = 0, ?Throwable $previous = null)
13+
{
14+
parent::__construct($message, $code, $previous);
15+
}
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Api\App\Factory;
6+
7+
use Api\App\Command\TokenGenerateCommand;
8+
use Api\App\Service\ErrorReportServiceInterface;
9+
use Psr\Container\ContainerExceptionInterface;
10+
use Psr\Container\ContainerInterface;
11+
use Psr\Container\NotFoundExceptionInterface;
12+
13+
class TokenGenerateCommandFactory
14+
{
15+
/**
16+
* @param ContainerInterface $container
17+
* @return TokenGenerateCommand
18+
* @throws ContainerExceptionInterface
19+
* @throws NotFoundExceptionInterface
20+
*/
21+
public function __invoke(ContainerInterface $container): TokenGenerateCommand
22+
{
23+
return new TokenGenerateCommand(
24+
$container->get(ErrorReportServiceInterface::class)
25+
);
26+
}
27+
}

src/App/src/Log/Handler/ErrorReportHandler.php

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44

55
namespace Api\App\Log\Handler;
66

7+
use Api\App\Exception\ForbiddenException;
78
use Api\App\Handler\DefaultHandler;
89
use Api\App\Message;
910
use Api\App\Service\ErrorReportServiceInterface;
1011
use Dot\AnnotatedServices\Annotation\Inject;
1112
use Dot\AnnotatedServices\Annotation\Service;
13+
use Laminas\Http\Response;
1214
use Mezzio\Hal\HalResponseFactory;
1315
use Mezzio\Hal\ResourceGenerator;
1416
use Psr\Http\Message\ResponseInterface;
@@ -56,14 +58,15 @@ public function post(ServerRequestInterface $request): ResponseInterface
5658
{
5759
try {
5860
$this->errorReportService
59-
->checkStatus()
6061
->checkRequest($request)
6162
->appendMessage(
6263
$request->getParsedBody()['message'] ?? ''
6364
);
6465
return $this->infoResponse(Message::ERROR_REPORT_OK);
66+
} catch (ForbiddenException $exception) {
67+
return $this->errorResponse($exception->getMessage(), Response::STATUS_CODE_403);
6568
} catch (Throwable $exception) {
66-
return $this->errorResponse($exception->getMessage());
69+
return $this->errorResponse($exception->getMessage(), Response::STATUS_CODE_500);
6770
}
6871
}
6972
}

src/App/src/Message.php

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,11 @@ class Message
2020
public const DUPLICATE_EMAIL = 'An account with this email address already exists.';
2121
public const DUPLICATE_IDENTITY = 'An account with this identity already exists.';
2222
public const ERROR_REPORT_OK = 'Error report successfully saved.';
23-
public const ERROR_REPORT_NOT_ALLOWED = 'This host is not allowed to report logs.';
23+
public const ERROR_REPORT_NOT_ALLOWED = 'You are not allowed to report errors.';
2424
public const ERROR_REPORT_NOT_ENABLED = 'Remote error reporting is not enabled.';
2525
public const INVALID_ACTIVATION_CODE = 'Invalid activation code.';
2626
public const INVALID_CLIENT_ID = 'Invalid client_id.';
27+
public const INVALID_CONFIG = 'Invalid configuration value: \'%s\'';
2728
public const INVALID_EMAIL = 'Invalid email.';
2829
public const INVALID_VALUE = 'The value specified for \'%s\' is invalid.';
2930
public const INVALID_IDENTIFIER = 'User cannot be found by supplied identifier.';

0 commit comments

Comments
 (0)