Passed
Push — master ( 6db377...b978c0 )
by Adrien
07:25 queued 04:21
created

EventCompleter::redactSensitiveData()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 14
c 0
b 0
f 0
dl 0
loc 19
ccs 15
cts 15
cp 1
rs 9.7998
cc 4
nc 4
nop 1
crap 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Ecodev\Felix\Log;
6
7
use Ecodev\Felix\Model\CurrentUser;
8
use Laminas\Log\Processor\ProcessorInterface;
9
10
class EventCompleter implements ProcessorInterface
11
{
12 5
    public function __construct(private readonly string $baseUrl)
13
    {
14 5
    }
15
16
    /**
17
     * Complete a log event with extra data, including stacktrace and any global stuff relevant to the app.
18
     */
19 4
    public function process(array $event): array
20
    {
21 4
        $envData = $this->getEnvData();
22 4
        $event = array_merge($event, $envData);
23
24
        // If we are logging PHP errors, then we include all known information in message
25 4
        if ($event['extra']['errno'] ?? false) {
26 1
            $event['message'] .= "\nStacktrace:\n" . $this->getStacktrace();
27
        }
28
29
        // Security hide clear text password
30 4
        if (isset($event['extra'])) {
31 3
            $event['extra'] = $this->redactSensitiveData($event['extra']);
32
        }
33
34 4
        return $event;
35
    }
36
37
    /**
38
     * Retrieve dynamic information from environment to be logged.
39
     */
40 4
    private function getEnvData(): array
41
    {
42 4
        $user = CurrentUser::get();
43
44 4
        if (PHP_SAPI === 'cli') {
45
            global $argv;
46 4
            $ip = !empty(getenv('REMOTE_ADDR')) ? getenv('REMOTE_ADDR') : 'script';
47 4
            $url = implode(' ', $argv);
48 4
            $referer = '';
49
        } else {
50
            $ip = $_SERVER['REMOTE_ADDR'] ?? '';
51
            $url = $this->baseUrl . $_SERVER['REQUEST_URI'];
52
            $referer = $_SERVER['HTTP_REFERER'] ?? '';
53
        }
54
55 4
        $request = $_REQUEST;
56 4
        $request = $this->redactSensitiveData($request);
57
58 4
        $envData = [
59 4
            'creator_id' => $user?->getId(),
60 4
            'login' => $user?->getLogin(),
61 4
            'url' => $url,
62 4
            'referer' => $referer,
63 4
            'request' => json_encode($request, JSON_PRETTY_PRINT),
64 4
            'ip' => $ip,
65 4
        ];
66
67 4
        return $envData;
68
    }
69
70
    /**
71
     * Redact sensitive values from the entire data structure.
72
     */
73 4
    private function redactSensitiveData(array $request): array
74
    {
75 4
        foreach ($request as $key => &$value) {
76 4
            if (in_array($key, [
77 4
                'password',
78 4
                'passwordConfirmation',
79 4
                'password_rep',
80 4
                'cpass',
81 4
                'npass1',
82 4
                'npass2',
83 4
                'password',
84 4
            ], true)) {
85 1
                $value = '***REDACTED***';
86 4
            } elseif (is_array($value)) {
87 1
                $value = $this->redactSensitiveData($value);
88
            }
89
        }
90
91 4
        return $request;
92
    }
93
94
    /**
95
     * Returns the backtrace excluding the most recent calls to this function, so we only get the interesting parts.
96
     */
97 1
    private function getStacktrace(): string
98
    {
99 1
        ob_start();
100 1
        @debug_print_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
101 1
        $trace = ob_get_contents();
102 1
        ob_end_clean();
103
104 1
        if ($trace === false) {
105
            return 'Could not get stacktrace';
106
        }
107
108
        // Remove first items from backtrace as it's this function and previous logging functions which is not interesting
109 1
        $shortenTrace = preg_replace('/^#[0-4]\s+[^\n]*\n/m', '', $trace);
110
111 1
        if ($shortenTrace === null) {
112
            return $trace;
113
        }
114
115
        // Renumber backtrace items.
116 1
        $renumberedTrace = preg_replace_callback('/^#(\d+)/m', fn ($matches) => '#' . ((int) $matches[1] - 5), $shortenTrace);
117
118 1
        if ($renumberedTrace === null) {
119
            return $shortenTrace;
120
        }
121
122 1
        return $renumberedTrace;
123
    }
124
}
125