Proyectos de Subversion Moodle

Rev

| Ultima modificación | Ver Log |

Rev Autor Línea Nro. Línea
1 efrain 1
<?php
2
namespace Aws;
3
 
4
use Aws\Api\Service;
5
use Aws\Exception\AwsException;
6
use GuzzleHttp\Promise\RejectedPromise;
7
use Psr\Http\Message\RequestInterface;
8
use Psr\Http\Message\ResponseInterface;
9
use Psr\Http\Message\StreamInterface;
10
use RecursiveArrayIterator;
11
use RecursiveIteratorIterator;
12
 
13
/**
14
 * Traces state changes between middlewares.
15
 */
16
class TraceMiddleware
17
{
18
    private $prevOutput;
19
    private $prevInput;
20
    private $config;
21
 
22
    /** @var Service */
23
    private $service;
24
 
25
    private static $authHeaders = [
26
        'X-Amz-Security-Token' => '[TOKEN]',
27
    ];
28
 
29
    private static $authStrings = [
30
        // S3Signature
31
        '/AWSAccessKeyId=[A-Z0-9]{20}&/i' => 'AWSAccessKeyId=[KEY]&',
32
        // SignatureV4 Signature and S3Signature
33
        '/Signature=.+/i' => 'Signature=[SIGNATURE]',
34
        // SignatureV4 access key ID
35
        '/Credential=[A-Z0-9]{20}\//i' => 'Credential=[KEY]/',
36
        // S3 signatures
37
        '/AWS [A-Z0-9]{20}:.+/' => 'AWS AKI[KEY]:[SIGNATURE]',
38
        // STS Presigned URLs
39
        '/X-Amz-Security-Token=[^&]+/i' => 'X-Amz-Security-Token=[TOKEN]',
40
        // Crypto *Stream Keys
41
        '/\["key.{27,36}Stream.{9}\]=>\s+.{7}\d{2}\) "\X{16,64}"/U' => '["key":[CONTENT KEY]]',
42
    ];
43
 
44
    /**
45
     * Configuration array can contain the following key value pairs.
46
     *
47
     * - logfn: (callable) Function that is invoked with log messages. By
48
     *   default, PHP's "echo" function will be utilized.
49
     * - stream_size: (int) When the size of a stream is greater than this
50
     *   number, the stream data will not be logged. Set to "0" to not log any
51
     *   stream data.
52
     * - scrub_auth: (bool) Set to false to disable the scrubbing of auth data
53
     *   from the logged messages.
54
     * - http: (bool) Set to false to disable the "debug" feature of lower
55
     *   level HTTP adapters (e.g., verbose curl output).
56
     * - auth_strings: (array) A mapping of authentication string regular
57
     *   expressions to scrubbed strings. These mappings are passed directly to
58
     *   preg_replace (e.g., preg_replace($key, $value, $debugOutput) if
59
     *   "scrub_auth" is set to true.
60
     * - auth_headers: (array) A mapping of header names known to contain
61
     *   sensitive data to what the scrubbed value should be. The value of any
62
     *   headers contained in this array will be replaced with the if
63
     *   "scrub_auth" is set to true.
64
     */
65
    public function __construct(array $config = [], Service $service = null)
66
    {
67
        $this->config = $config + [
68
            'logfn'        => function ($value) { echo $value; },
69
            'stream_size'  => 524288,
70
            'scrub_auth'   => true,
71
            'http'         => true,
72
            'auth_strings' => [],
73
            'auth_headers' => [],
74
        ];
75
 
76
        $this->config['auth_strings'] += self::$authStrings;
77
        $this->config['auth_headers'] += self::$authHeaders;
78
        $this->service = $service;
79
    }
80
 
81
    public function __invoke($step, $name)
82
    {
83
        $this->prevOutput = $this->prevInput = [];
84
 
85
        return function (callable $next) use ($step, $name) {
86
            return function (
87
                CommandInterface $command,
88
                RequestInterface $request = null
89
            ) use ($next, $step, $name) {
90
                $this->createHttpDebug($command);
91
                $start = microtime(true);
92
                $this->stepInput([
93
                    'step'    => $step,
94
                    'name'    => $name,
95
                    'request' => $this->requestArray($request),
96
                    'command' => $this->commandArray($command)
97
                ]);
98
 
99
                return $next($command, $request)->then(
100
                    function ($value) use ($step, $name, $command, $start) {
101
                        $this->flushHttpDebug($command);
102
                        $this->stepOutput($start, [
103
                            'step'   => $step,
104
                            'name'   => $name,
105
                            'result' => $this->resultArray($value),
106
                            'error'  => null
107
                        ]);
108
                        return $value;
109
                    },
110
                    function ($reason) use ($step, $name, $start, $command) {
111
                        $this->flushHttpDebug($command);
112
                        $this->stepOutput($start, [
113
                            'step'   => $step,
114
                            'name'   => $name,
115
                            'result' => null,
116
                            'error'  => $this->exceptionArray($reason)
117
                        ]);
118
                        return new RejectedPromise($reason);
119
                    }
120
                );
121
            };
122
        };
123
    }
124
 
125
    private function stepInput($entry)
126
    {
127
        static $keys = ['command', 'request'];
128
        $this->compareStep($this->prevInput, $entry, '-> Entering', $keys);
129
        $this->write("\n");
130
        $this->prevInput = $entry;
131
    }
132
 
133
    private function stepOutput($start, $entry)
134
    {
135
        static $keys = ['result', 'error'];
136
        $this->compareStep($this->prevOutput, $entry, '<- Leaving', $keys);
137
        $totalTime = microtime(true) - $start;
138
        $this->write("  Inclusive step time: " . $totalTime . "\n\n");
139
        $this->prevOutput = $entry;
140
    }
141
 
142
    private function compareStep(array $a, array $b, $title, array $keys)
143
    {
144
        $changes = [];
145
        foreach ($keys as $key) {
146
            $av = isset($a[$key]) ? $a[$key] : null;
147
            $bv = isset($b[$key]) ? $b[$key] : null;
148
            $this->compareArray($av, $bv, $key, $changes);
149
        }
150
        $str = "\n{$title} step {$b['step']}, name '{$b['name']}'";
151
        $str .= "\n" . str_repeat('-', strlen($str) - 1) . "\n\n  ";
152
        $str .= $changes
153
            ? implode("\n  ", str_replace("\n", "\n  ", $changes))
154
            : 'no changes';
155
        $this->write($str . "\n");
156
    }
157
 
158
    private function commandArray(CommandInterface $cmd)
159
    {
160
        return [
161
            'instance' => spl_object_hash($cmd),
162
            'name'     => $cmd->getName(),
163
            'params'   => $this->getRedactedArray($cmd)
164
        ];
165
    }
166
 
167
    private function requestArray(RequestInterface $request = null)
168
    {
169
        return !$request ? [] : array_filter([
170
            'instance' => spl_object_hash($request),
171
            'method'   => $request->getMethod(),
172
            'headers'  => $this->redactHeaders($request->getHeaders()),
173
            'body'     => $this->streamStr($request->getBody()),
174
            'scheme'   => $request->getUri()->getScheme(),
175
            'port'     => $request->getUri()->getPort(),
176
            'path'     => $request->getUri()->getPath(),
177
            'query'    => $request->getUri()->getQuery(),
178
        ]);
179
    }
180
 
181
    private function responseArray(ResponseInterface $response = null)
182
    {
183
        return !$response ? [] : [
184
            'instance'   => spl_object_hash($response),
185
            'statusCode' => $response->getStatusCode(),
186
            'headers'    => $this->redactHeaders($response->getHeaders()),
187
            'body'       => $this->streamStr($response->getBody())
188
        ];
189
    }
190
 
191
    private function resultArray($value)
192
    {
193
        return $value instanceof ResultInterface
194
            ? [
195
                'instance' => spl_object_hash($value),
196
                'data'     => $value->toArray()
197
            ] : $value;
198
    }
199
 
200
    private function exceptionArray($e)
201
    {
202
        if (!($e instanceof \Exception)) {
203
            return $e;
204
        }
205
 
206
        $result = [
207
            'instance'   => spl_object_hash($e),
208
            'class'      => get_class($e),
209
            'message'    => $e->getMessage(),
210
            'file'       => $e->getFile(),
211
            'line'       => $e->getLine(),
212
            'trace'      => $e->getTraceAsString(),
213
        ];
214
 
215
        if ($e instanceof AwsException) {
216
            $result += [
217
                'type'       => $e->getAwsErrorType(),
218
                'code'       => $e->getAwsErrorCode(),
219
                'requestId'  => $e->getAwsRequestId(),
220
                'statusCode' => $e->getStatusCode(),
221
                'result'     => $this->resultArray($e->getResult()),
222
                'request'    => $this->requestArray($e->getRequest()),
223
                'response'   => $this->responseArray($e->getResponse()),
224
            ];
225
        }
226
 
227
        return $result;
228
    }
229
 
230
    private function compareArray($a, $b, $path, array &$diff)
231
    {
232
        if ($a === $b) {
233
            return;
234
        }
235
 
236
        if (is_array($a)) {
237
            $b = (array) $b;
238
            $keys = array_unique(array_merge(array_keys($a), array_keys($b)));
239
            foreach ($keys as $k) {
240
                if (!array_key_exists($k, $a)) {
241
                    $this->compareArray(null, $b[$k], "{$path}.{$k}", $diff);
242
                } elseif (!array_key_exists($k, $b)) {
243
                    $this->compareArray($a[$k], null, "{$path}.{$k}", $diff);
244
                } else {
245
                    $this->compareArray($a[$k], $b[$k], "{$path}.{$k}", $diff);
246
                }
247
            }
248
        } elseif ($a !== null && $b === null) {
249
            $diff[] = "{$path} was unset";
250
        } elseif ($a === null && $b !== null) {
251
            $diff[] = sprintf("%s was set to %s", $path, $this->str($b));
252
        } else {
253
            $diff[] = sprintf("%s changed from %s to %s", $path, $this->str($a), $this->str($b));
254
        }
255
    }
256
 
257
    private function str($value)
258
    {
259
        if (is_scalar($value)) {
260
            return (string) $value;
261
        }
262
 
263
        if ($value instanceof \Exception) {
264
            $value = $this->exceptionArray($value);
265
        }
266
 
267
        ob_start();
268
        var_dump($value);
269
        return ob_get_clean();
270
    }
271
 
272
    private function streamStr(StreamInterface $body)
273
    {
274
        return $body->getSize() < $this->config['stream_size']
275
            ? (string) $body
276
            : 'stream(size=' . $body->getSize() . ')';
277
    }
278
 
279
    private function createHttpDebug(CommandInterface $command)
280
    {
281
        if ($this->config['http'] && !isset($command['@http']['debug'])) {
282
            $command['@http']['debug'] = fopen('php://temp', 'w+');
283
        }
284
    }
285
 
286
    private function flushHttpDebug(CommandInterface $command)
287
    {
288
        if ($res = $command['@http']['debug']) {
289
            if (is_resource($res)) {
290
                rewind($res);
291
                $this->write(stream_get_contents($res));
292
                fclose($res);
293
            }
294
            $command['@http']['debug'] = null;
295
        }
296
    }
297
 
298
    private function write($value)
299
    {
300
        if ($this->config['scrub_auth']) {
301
            foreach ($this->config['auth_strings'] as $pattern => $replacement) {
302
                $value = preg_replace_callback(
303
                    $pattern,
304
                    function ($matches) use ($replacement) {
305
                        return $replacement;
306
                    },
307
                    $value
308
                );
309
            }
310
        }
311
 
312
        call_user_func($this->config['logfn'], $value);
313
    }
314
 
315
    private function redactHeaders(array $headers)
316
    {
317
        if ($this->config['scrub_auth']) {
318
            $headers = $this->config['auth_headers'] + $headers;
319
        }
320
 
321
        return $headers;
322
    }
323
 
324
    /**
325
     * @param CommandInterface $cmd
326
     * @return array
327
     */
328
    private function getRedactedArray(CommandInterface $cmd)
329
    {
330
        if (!isset($this->service["shapes"])) {
331
            return $cmd->toArray();
332
        }
333
        $shapes = $this->service["shapes"];
334
        $cmdArray = $cmd->toArray();
335
        $iterator = new RecursiveIteratorIterator(
336
            new RecursiveArrayIterator($cmdArray),
337
            RecursiveIteratorIterator::SELF_FIRST
338
        );
339
        foreach ($iterator as $parameter => $value) {
340
           if (isset($shapes[$parameter]['sensitive']) &&
341
               $shapes[$parameter]['sensitive'] === true
342
           ) {
343
               $redactedValue = is_string($value) ? "[{$parameter}]" : ["[{$parameter}]"];
344
               $currentDepth = $iterator->getDepth();
345
               for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) {
346
                   $subIterator = $iterator->getSubIterator($subDepth);
347
                   $subIterator->offsetSet(
348
                       $subIterator->key(),
349
                       ($subDepth === $currentDepth
350
                           ? $redactedValue
351
                           : $iterator->getSubIterator(($subDepth+1))->getArrayCopy()
352
                       )
353
                   );
354
               }
355
           }
356
        }
357
        return $iterator->getArrayCopy();
358
    }
359
}