This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php declare(strict_types=1); |
||
2 | |||
3 | namespace ApiClients\Middleware\Log; |
||
4 | |||
5 | use ApiClients\Foundation\Middleware\Annotation\Last; |
||
6 | use ApiClients\Foundation\Middleware\MiddlewareInterface; |
||
7 | use Psr\Http\Message\RequestInterface; |
||
8 | use Psr\Http\Message\ResponseInterface; |
||
9 | use Psr\Http\Message\UriInterface; |
||
10 | use Psr\Log\LoggerInterface; |
||
11 | use React\Promise\CancellablePromiseInterface; |
||
12 | use function React\Promise\reject; |
||
13 | use function React\Promise\resolve; |
||
14 | use Throwable; |
||
15 | use function WyriHaximus\getIn; |
||
16 | |||
17 | class LoggerMiddleware implements MiddlewareInterface |
||
18 | { |
||
19 | private const REQUEST = 'request'; |
||
20 | private const RESPONSE = 'response'; |
||
21 | private const ERROR = 'error'; |
||
22 | |||
23 | private const MESSAGE_URL = '[{{transaction_id}}] Requesting: {{request.uri}}'; |
||
24 | private const MESSAGE_SUCCESSFUL = '[{{transaction_id}}] Request completed with {{response.status_code}}'; |
||
25 | private const MESSAGE_ERROR = '[{{transaction_id}}] {{error.message}}'; |
||
26 | |||
27 | /** |
||
28 | * @var LoggerInterface |
||
29 | */ |
||
30 | private $logger; |
||
31 | |||
32 | private $context = []; |
||
33 | |||
34 | 6 | public function __construct(LoggerInterface $logger) |
|
35 | { |
||
36 | 6 | $this->logger = $logger; |
|
37 | 6 | } |
|
38 | |||
39 | /** |
||
40 | * @Last() |
||
41 | */ |
||
42 | 6 | public function pre( |
|
43 | RequestInterface $request, |
||
44 | string $transactionId, |
||
45 | array $options = [] |
||
46 | ): CancellablePromiseInterface { |
||
47 | 6 | if (!isset($options[self::class][Options::LEVEL]) && !isset($options[self::class][Options::ERROR_LEVEL])) { |
|
48 | 1 | return resolve($request); |
|
49 | } |
||
50 | |||
51 | 5 | $this->context[$transactionId] = [ |
|
52 | 5 | 'transaction_id' => $transactionId, |
|
53 | 5 | self::REQUEST => [], |
|
54 | ]; |
||
55 | 5 | $this->context[$transactionId][self::REQUEST]['method'] = $request->getMethod(); |
|
56 | 5 | $this->context[$transactionId][self::REQUEST]['uri'] = (string)$this->stripQueryItems( |
|
57 | 5 | $request->getUri(), |
|
58 | 5 | $options |
|
59 | ); |
||
60 | 5 | $this->context[$transactionId][self::REQUEST]['protocol_version'] = (string)$request->getProtocolVersion(); |
|
61 | 5 | $ignoreHeaders = $options[self::class][Options::IGNORE_HEADERS] ?? []; |
|
62 | 5 | $this->context[$transactionId] = $this->iterateHeaders( |
|
63 | 5 | $this->context[$transactionId], |
|
64 | 5 | self::REQUEST, |
|
65 | 5 | $request->getHeaders(), |
|
66 | 5 | $ignoreHeaders |
|
67 | ); |
||
68 | |||
69 | 5 | if (!isset($options[self::class][Options::URL_LEVEL])) { |
|
70 | 3 | return resolve($request); |
|
71 | } |
||
72 | |||
73 | 2 | $message = $this->renderTemplate( |
|
74 | 2 | $options[self::class][Options::MESSAGE_PRE] ?? self::MESSAGE_URL, |
|
75 | 2 | $this->context[$transactionId] |
|
76 | ); |
||
77 | 2 | $this->logger->log($options[self::class][Options::URL_LEVEL], $message, $this->context[$transactionId]); |
|
78 | |||
79 | 2 | return resolve($request); |
|
80 | } |
||
81 | |||
82 | /** |
||
83 | * @Last() |
||
84 | */ |
||
85 | 3 | public function post( |
|
86 | ResponseInterface $response, |
||
87 | string $transactionId, |
||
88 | array $options = [] |
||
89 | ): CancellablePromiseInterface { |
||
90 | 3 | if (!isset($this->context[$transactionId])) { |
|
91 | 1 | return resolve($response); |
|
92 | } |
||
93 | |||
94 | 2 | $context = $this->context[$transactionId]; |
|
95 | 2 | if (!isset($options[self::class][Options::LEVEL]) && !isset($options[self::class][Options::ERROR_LEVEL])) { |
|
96 | unset($this->context[$transactionId]); |
||
97 | } |
||
98 | |||
99 | 2 | if (!isset($options[self::class][Options::LEVEL])) { |
|
100 | return resolve($response); |
||
101 | } |
||
102 | |||
103 | 2 | $context = $this->addResponseToContext($context, $response, $options); |
|
104 | 2 | $message = $this->renderTemplate( |
|
105 | 2 | $options[self::class][Options::MESSAGE_POST] ?? self::MESSAGE_SUCCESSFUL, |
|
106 | 2 | $context |
|
107 | ); |
||
108 | 2 | $this->logger->log($options[self::class][Options::LEVEL], $message, $context); |
|
109 | |||
110 | 2 | return resolve($response); |
|
111 | } |
||
112 | |||
113 | /** |
||
114 | * @Last() |
||
115 | */ |
||
116 | 4 | public function error( |
|
117 | Throwable $throwable, |
||
118 | string $transactionId, |
||
119 | array $options = [] |
||
120 | ): CancellablePromiseInterface { |
||
121 | 4 | if (!isset($this->context[$transactionId])) { |
|
122 | 1 | return reject($throwable); |
|
123 | } |
||
124 | |||
125 | 3 | $context = $this->context[$transactionId]; |
|
126 | 3 | unset($this->context[$transactionId]); |
|
127 | |||
128 | 3 | if (!isset($options[self::class][Options::ERROR_LEVEL])) { |
|
129 | return reject($throwable); |
||
130 | } |
||
131 | |||
132 | 3 | $response = null; |
|
133 | 3 | if (\method_exists($throwable, 'getResponse')) { |
|
134 | 1 | $response = $throwable->getResponse(); |
|
0 ignored issues
–
show
|
|||
135 | } |
||
136 | 3 | if ($response instanceof ResponseInterface) { |
|
137 | 1 | $context = $this->addResponseToContext($context, $response, $options); |
|
138 | } |
||
139 | |||
140 | 3 | $context[self::ERROR]['message'] = $throwable->getMessage(); |
|
141 | 3 | $context[self::ERROR]['code'] = $throwable->getCode(); |
|
142 | 3 | $context[self::ERROR]['file'] = $throwable->getFile(); |
|
143 | 3 | $context[self::ERROR]['line'] = $throwable->getLine(); |
|
144 | 3 | $context[self::ERROR]['trace'] = $throwable->getTraceAsString(); |
|
145 | |||
146 | 3 | if (\method_exists($throwable, 'getContext')) { |
|
147 | $context[self::ERROR]['context'] = $throwable->getContext(); |
||
148 | } |
||
149 | |||
150 | 3 | $message = $this->renderTemplate( |
|
151 | 3 | $options[self::class][Options::MESSAGE_ERROR] ?? self::MESSAGE_ERROR, |
|
152 | 3 | $context |
|
153 | ); |
||
154 | 3 | $this->logger->log($options[self::class][Options::ERROR_LEVEL], $message, $context); |
|
155 | |||
156 | 3 | return reject($throwable); |
|
157 | } |
||
158 | |||
159 | 5 | protected function iterateHeaders( |
|
160 | array $context, |
||
161 | string $prefix, |
||
162 | array $headers, |
||
163 | array $ignoreHeaders |
||
164 | ): array { |
||
165 | 5 | foreach ($headers as $header => $value) { |
|
166 | 5 | if (\in_array($header, $ignoreHeaders, true)) { |
|
167 | 5 | continue; |
|
168 | } |
||
169 | |||
170 | 5 | $context[$prefix]['headers'][$header] = $value; |
|
171 | } |
||
172 | |||
173 | 5 | return $context; |
|
174 | } |
||
175 | |||
176 | 3 | private function addResponseToContext( |
|
177 | array $context, |
||
178 | ResponseInterface $response, |
||
179 | array $options |
||
180 | ): array { |
||
181 | 3 | $context[self::RESPONSE]['status_code'] = $response->getStatusCode(); |
|
182 | 3 | $context[self::RESPONSE]['status_reason'] = $response->getReasonPhrase(); |
|
183 | 3 | $context[self::RESPONSE]['protocol_version'] = $response->getProtocolVersion(); |
|
184 | 3 | $ignoreHeaders = $options[self::class][Options::IGNORE_HEADERS] ?? []; |
|
185 | 3 | $context = $this->iterateHeaders( |
|
186 | 3 | $context, |
|
187 | 3 | self::RESPONSE, |
|
188 | 3 | $response->getHeaders(), |
|
189 | 3 | $ignoreHeaders |
|
190 | ); |
||
191 | |||
192 | 3 | return $context; |
|
193 | } |
||
194 | |||
195 | 5 | private function stripQueryItems(UriInterface $uri, array $options): UriInterface |
|
196 | { |
||
197 | 5 | \parse_str($uri->getQuery(), $query); |
|
198 | 5 | foreach ($options[self::class][Options::IGNORE_URI_QUERY_ITEMS] ?? [] as $item) { |
|
199 | 2 | unset($query[$item], $query[$item . '[]']); |
|
200 | } |
||
201 | |||
202 | 5 | return $uri->withQuery(\http_build_query($query)); |
|
203 | } |
||
204 | |||
205 | 5 | private function renderTemplate(string $template, array $context): string |
|
206 | { |
||
207 | 5 | $keyValues = []; |
|
208 | 5 | \preg_match_all("|\{\{(.*)\}\}|U", $template, $out, \PREG_PATTERN_ORDER); |
|
209 | 5 | foreach (\array_unique(\array_values($out[1])) as $placeHolder) { |
|
210 | 5 | $keyValues['{{' . $placeHolder . '}}'] = getIn($context, $placeHolder, ''); |
|
211 | } |
||
212 | 5 | $template = \str_replace(\array_keys($keyValues), \array_values($keyValues), $template); |
|
213 | |||
214 | 5 | return $template; |
|
215 | } |
||
216 | } |
||
217 |
Let’s take a look at an example:
In the above example, the authenticate() method works fine as long as you just pass instances of MyUser. However, if you now also want to pass a different implementation of User which does not have a getDisplayName() method, the code will break.
Available Fixes
Change the type-hint for the parameter:
Add an additional type-check:
Add the method to the interface: