Skip to content

Commit 08fab51

Browse files
committed
Updating Field Masker and adding additional tests
1 parent efe56a8 commit 08fab51

File tree

2 files changed

+96
-108
lines changed

2 files changed

+96
-108
lines changed

src/Masking/FieldMasker.php

Lines changed: 50 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -6,136 +6,79 @@
66

77
final class FieldMasker
88
{
9-
/**
10-
* Create a new instance of the FieldMasker.
11-
* @param array<int,string|int|bool|array> $fields
12-
*/
139
public function __construct(
1410
public array $fields = [],
1511
) {
1612
}
1713

18-
/**
19-
* Mask the inputted data.
20-
* @param array<string,string|array> $data
21-
* @return array
22-
*/
2314
public function mask(array $data): array
2415
{
2516
$collector = [];
2617
foreach ($data as $key => $value) {
27-
if (is_array($value)) {
28-
$collector[$key] = $this->mask(
18+
$collector[$key] = match (true) {
19+
is_array($value) => $this->mask(
2920
data: $value,
30-
);
31-
}
21+
),
22+
is_string($value) => $this->handleString(
23+
key: $key,
24+
value: $value,
25+
),
26+
default => $value,
27+
};
28+
}
3229

33-
if (is_bool($value) || is_int($value) || is_float($value) || is_null($value)) {
34-
$collector[$key] = $value;
35-
}
30+
return $collector;
31+
}
3632

37-
// we should know it is a string.
38-
if (is_string($value)) {
39-
// check if this is an auth header or api key header etc
40-
// is the key a header we want to mask?
41-
if ($this->isHeader(
42-
name: $key,
43-
)) {
44-
// grab the sensitive part of the value and mask.
45-
if ($this->isAuth(
46-
value: $value,
47-
)) {
48-
$parts = explode(
49-
separator: ' ',
50-
string: $value,
51-
);
52-
53-
if (count($parts) >= 2) {
54-
for ($i = 1; $i < count($parts); $i++) {
55-
$parts[$i] = $this->star(
56-
string: $parts[$i]
57-
);
58-
}
59-
} else {
60-
$parts[0] = $this->star($parts[0]);
61-
}
62-
63-
$value = implode(' ', $parts);
64-
} else {
65-
$value = $this->star(
66-
string: $value,
67-
);
68-
}
69-
}
70-
71-
if (in_array($key, $this->fields, true)) {
72-
$collector[$key] = $this->star(
73-
string: $value,
74-
);
75-
} else {
76-
$collector[$key] = $value;
77-
}
78-
}
33+
private function handleString(string $key, string $value): string
34+
{
35+
static $lowerFields = null;
36+
if ($lowerFields === null) {
37+
$lowerFields = array_map('strtolower', $this->fields);
7938
}
8039

81-
return $collector;
40+
$lowerKey = strtolower($key);
41+
42+
if (in_array($lowerKey, $lowerFields, true)) {
43+
return $this->star($value);
44+
}
45+
46+
if ($this->isSensitiveHeader($lowerKey)) {
47+
return $this->maskAuthorization($value);
48+
}
49+
50+
if ($this->isBase64($value)) {
51+
return 'base64 encoded images are too big to process';
52+
}
53+
54+
return $value;
8255
}
8356

84-
/**
85-
* Check if the field is a Header.
86-
* @param int|bool|float|string|null $name
87-
* @return bool
88-
*/
89-
private function isHeader(int|bool|float|null|string $name): bool
57+
private function maskAuthorization(string $value): string
9058
{
91-
return in_array(
92-
needle: $name,
93-
haystack: [
94-
'auth',
95-
'Auth',
96-
'Authorization',
97-
'authorization',
98-
'X-API-KEY',
99-
'x-api-key',
100-
],
101-
strict: true,
102-
);
59+
$parts = explode(' ', $value, 2);
60+
if (isset($parts[1])) {
61+
$authTypeLower = strtolower($parts[0]);
62+
if (in_array($authTypeLower, ['bearer', 'basic', 'digest'])) {
63+
return $parts[0].' '.$this->star($parts[1]);
64+
}
65+
}
66+
67+
return $this->star($value);
10368
}
10469

105-
/**
106-
* Check is the value is part of an Auth header.
107-
* @param string $value
108-
* @return bool
109-
*/
110-
private function isAuth(string $value): bool
70+
private function isSensitiveHeader(string $key): bool
11171
{
112-
return in_array(
113-
needle: explode(
114-
separator: ' ',
115-
string: $value,
116-
)[0],
117-
haystack: [
118-
'Bearer',
119-
'bearer',
120-
'Basic',
121-
'basic',
122-
],
123-
strict: true,
124-
);
72+
return in_array($key, ['authorization', 'x-api-key'], true);
12573
}
12674

127-
/**
128-
* Replace a string input with a star.
129-
* @param string $string
130-
* @return string
131-
*/
13275
public function star(string $string): string
13376
{
134-
return str_repeat(
135-
string: '*',
136-
times: strlen(
137-
string: $string,
138-
),
139-
);
77+
return str_repeat('*', strlen($string));
78+
}
79+
80+
private function isBase64(string $string): bool
81+
{
82+
return str_starts_with($string, 'data:image/') && str_contains($string, ';base64,');
14083
}
14184
}

tests/Masking/FieldMaskerTest.php

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@
134134
'password' => '********',
135135
'api_key' => '****',
136136
],
137-
'Authorization' => 'Bearer *************** ***',
137+
'Authorization' => 'Bearer *******************',
138138
'X-API-KEY' => '**************',
139139
'cc' => '*******************',
140140
'foo' => 'bar',
@@ -168,3 +168,48 @@
168168
'foo' => 'bar',
169169
]);
170170
});
171+
172+
it('masks base64 encoded image strings', function () {
173+
$masker = new FieldMasker();
174+
175+
$base64Image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==';
176+
$data = [
177+
'image' => $base64Image,
178+
];
179+
180+
$maskedData = $masker->mask($data);
181+
182+
// Assert that the base64 encoded image string is replaced with a mask or default value
183+
expect($maskedData['image'])->not()->toEqual($base64Image);
184+
expect($maskedData['image'])->toEqual('base64 encoded images are too big to process'); // Assuming 'DEFAULT_VALUE' is what you use for masking
185+
});
186+
187+
it('does not mask non-base64 encoded strings', function () {
188+
$masker = new FieldMasker();
189+
190+
$nonBase64String = 'This is a test string, not base64 encoded.';
191+
$data = [
192+
'description' => $nonBase64String,
193+
];
194+
195+
$maskedData = $masker->mask($data);
196+
197+
// Assert that non-base64 encoded strings remain unchanged
198+
expect($maskedData['description'])->toEqual($nonBase64String);
199+
});
200+
201+
it('masks base64 encoded image strings within nested arrays', function () {
202+
$masker = new FieldMasker();
203+
204+
$base64Image = 'data:image/jpeg;base64,/9j/4AAQSkZJRgABAQAAAQABAAD/2wBDAAgGBgcGBQgHBwcJ...';
205+
$data = [
206+
'profile' => [
207+
'avatar' => $base64Image,
208+
],
209+
];
210+
211+
$maskedData = $masker->mask($data);
212+
213+
// Assert that the base64 encoded image string in a nested array is masked
214+
expect($maskedData)->toHaveKey('profile.avatar', 'base64 encoded images are too big to process');
215+
});

0 commit comments

Comments
 (0)