Lines
100.00%
24 / 24
Methods
100.00%
3 / 3
Classes
100.00%
1 / 1
| Name | Lines | Methods | CRAP | ||||
|---|---|---|---|---|---|---|---|
| redactQueryString | 100.00% | 12 / 12 | 100.00% | 1 / 1 | 5 | ||
| redactUrl | 100.00% | 11 / 11 | 100.00% | 1 / 1 | 3 | ||
| isSensitive | 100.00% | 1 / 1 | 100.00% | 1 / 1 | 1 | ||
| 28 | final readonly class TrackingDataRedactor | |
| 29 | { | |
| 30 | /** @var string Placeholder written in place of a sensitive value */ | |
| 31 | public const string REDACTED = '[redacted]'; | |
| 32 | ||
| 33 | /** | |
| 34 | * Lowercase names of query parameters whose values must never be logged. | |
| 35 | * | |
| 36 | * @var list<string> | |
| 37 | */ | |
| 38 | private const array SENSITIVE_PARAMETERS = [ | |
| 39 | 'sig', | |
| 40 | 'signature', | |
| 41 | 'token', | |
| 42 | 'csrf', | |
| 43 | 'csrftoken', | |
| 44 | 'key', | |
| 45 | 'secret', | |
| 46 | 'password', | |
| 47 | 'passwd', | |
| 48 | 'pwd', | |
| 49 | 'pass', | |
| 50 | 'auth', | |
| 51 | 'apikey', | |
| 52 | 'api_key', | |
| 53 | 'access_token', | |
| 54 | ]; | |
| 55 | ||
| 56 | /** | |
| 57 | * Redacts the values of sensitive parameters in a raw query string while | |
| 58 | * preserving parameter order and the values of non-sensitive parameters. | |
| 59 | */ | |
| 60 | public function redactQueryString(string $queryString): string | |
| 61 | { | |
| 62 | if ($queryString === '') { | |
| 63 | return ''; | |
| 64 | } | |
| 65 | ||
| 66 | $pairs = explode('&', $queryString); | |
| 67 | foreach ($pairs as $index => $pair) { | |
| 68 | $separatorPosition = strpos($pair, needle: '='); | |
| 69 | if ($separatorPosition === false) { | |
| 70 | continue; | |
| 71 | } | |
| 72 | ||
| 73 | $name = substr($pair, offset: 0, length: $separatorPosition); | |
| 74 | if (!$this->isSensitive($name)) { | |
| 75 | continue; | |
| 76 | } | |
| 77 | ||
| 78 | $pairs[$index] = $name . '=' . self::REDACTED; | |
| 79 | } | |
| 80 | ||
| 81 | return implode('&', $pairs); | |
| 82 | } | |
| 83 | ||
| 84 | /** | |
| 85 | * Redacts sensitive parameters in the query part of a URL (e.g. a referer), | |
| 86 | * leaving the scheme, host, path and any fragment untouched. | |
| 87 | */ | |
| 88 | public function redactUrl(string $url): string | |
| 89 | { | |
| 90 | $queryPosition = strpos($url, needle: '?'); | |
| 91 | if ($queryPosition === false) { | |
| 92 | return $url; | |
| 93 | } | |
| 94 | ||
| 95 | $base = substr($url, offset: 0, length: $queryPosition + 1); | |
| 96 | $query = substr($url, offset: $queryPosition + 1); | |
| 97 | ||
| 98 | $fragment = ''; | |
| 99 | $fragmentPosition = strpos($query, needle: '#'); | |
| 100 | if ($fragmentPosition !== false) { | |
| 101 | $fragment = substr($query, offset: $fragmentPosition); | |
| 102 | $query = substr($query, offset: 0, length: $fragmentPosition); | |
| 103 | } | |
| 104 | ||
| 105 | return $base . $this->redactQueryString($query) . $fragment; | |
| 106 | } | |
| 107 | ||
| 108 | private function isSensitive(string $name): bool | |
| 109 | { | |
| 110 | return in_array(strtolower(urldecode($name)), self::SENSITIVE_PARAMETERS, strict: true); | |
| 111 | } | |
| 112 | } |