-
Notifications
You must be signed in to change notification settings - Fork 1
/
Action.php
151 lines (132 loc) · 5.05 KB
/
Action.php
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
<?php
declare(strict_types=1);
namespace BigGive\Identity\Application\Actions;
use BigGive\Identity\Domain\DomainException\DomainRecordNotFoundException;
use OpenApi\Annotations as OA;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Log\LoggerInterface;
use Slim\Exception\HttpBadRequestException;
use Slim\Exception\HttpNotFoundException;
use Symfony\Component\Validator\Constraints\NotBlank;
use Symfony\Component\Validator\Constraints\NotCompromisedPassword;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* @OA\Info(title="Big Give Identity service", version="0.0.7"),
* @OA\Server(
* description="Staging",
* url="https://identity-staging.thebiggivetest.org.uk",
* ),
* @OA\SecurityScheme(
* securityScheme="personJWT",
* type="apiKey",
* in="header",
* name="x-tbg-auth",
* ),
*
* Swagger Hub doesn't (yet?) support `"bearerFormat": "JWT"`.
*/
abstract class Action
{
public function __construct(protected readonly LoggerInterface $logger)
{
}
/**
* @throws HttpNotFoundException
* @throws HttpBadRequestException
*/
public function __invoke(Request $request, Response $_response, array $args): Response
{
try {
return $this->action($request, $args);
} catch (DomainRecordNotFoundException $e) {
throw new HttpNotFoundException($request, $e->getMessage());
}
}
/**
* @param array $args
* @throws DomainRecordNotFoundException
* @throws HttpBadRequestException
*/
abstract protected function action(Request $request, array $args): Response;
/**
* @return mixed
* @throws HttpBadRequestException
*/
protected function resolveArg(array $args, Request $request, string $name)
{
if (!isset($args[$name])) {
throw new HttpBadRequestException($request, "Could not resolve argument `{$name}`.");
}
return $args[$name];
}
/**
* @param array|object|null $data
*/
protected function respondWithData($data = null, int $statusCode = 200): Response
{
$payload = new ActionPayload($statusCode, $data);
return $this->respond($payload);
}
protected function respond(ActionPayload $payload): Response
{
$response = new \Slim\Psr7\Response();
$json = json_encode($payload, JSON_PRETTY_PRINT);
$response->getBody()->write($json);
return $response
->withHeader('Content-Type', 'application/json')
->withStatus($payload->getStatusCode());
}
/**
* @param string $logMessage
* @param string|null $publicMessage Falls back to $logMessage if null.
* @param bool $reduceSeverity Whether to log this error only at INFO level. Used to
* avoid noise from known issues.
* @param int|null $httpCode Falls back to 400 if null.
* @return Response with 400 (or custom) HTTP response code.
*/
protected function validationError(
string $logMessage,
?string $publicMessage = null,
bool $reduceSeverity = false,
?int $httpCode = 400,
?string $errorType = null,
?string $htmlMessage = null,
): Response {
if ($reduceSeverity) {
$this->logger->info($logMessage);
} else {
$this->logger->warning($logMessage);
}
$errorType ??= ($httpCode === 401 ? ActionError::VALIDATION_ERROR : ActionError::BAD_REQUEST);
$error = new ActionError(
$errorType,
$publicMessage ?? $logMessage,
htmlDescription: $htmlMessage,
);
return $this->respond(new ActionPayload($httpCode, null, $error));
}
protected function summariseConstraintViolation(ConstraintViolationInterface $violation): string
{
if ($violation->getMessage() === 'This value should not be blank.') {
return "{$violation->getPropertyPath()} must not be blank";
}
return $violation->getMessage();
}
protected function summariseConstraintViolationAsHtmlSnippet(ConstraintViolationInterface $violation): string
{
return match ($violation->getCode()) {
NotBlank::IS_BLANK_ERROR => htmlspecialchars("{$violation->getPropertyPath()} must not be blank"),
NotCompromisedPassword::COMPROMISED_PASSWORD_ERROR =>
/* would prefer not to have color style here, but I'm having trouble
setting the colour in donate-fronted for some reason. */
<<<HTML
We use a password-checking service which has found this password in a data breach. Please choose a
different one. For more information please read our
<a style="color: inherit;" href="https://biggive.org/privacy/" target="_blank">Privacy Policy<a/>.
HTML,
default => htmlspecialchars((string) $violation->getMessage())
};
}
}