Skip to content

Commit 6acbb0c

Browse files
committed
Added skipKeyWhen method + refactor
1 parent 51f9c10 commit 6acbb0c

File tree

4 files changed

+114
-15
lines changed

4 files changed

+114
-15
lines changed

CHANGELOG.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,14 @@
22

33
All notable changes to `laravel-xss-protection` will be documented in this file.
44

5-
## 1.0.0 - 202X-XX-XX
5+
## 1.2.0 - 2022-02-03
6+
7+
- added `skipKeyWhen` method
8+
9+
## 1.1.0 / 1.1.1 - 2022-02-02
10+
11+
- added `MaliciousInputFound` event
12+
13+
## 1.0.0 - 2022-02-02
614

715
- initial release

README.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,18 @@ XssCleanInput::skipWhen(function (Request $request) {
6161
});
6262
```
6363

64+
You can also exclude keys by using the static `skipKeyWhen` method. This also allows you to interact with the value and request.
65+
66+
```php
67+
XssCleanInput::skipKeyWhen(function (string $key, $value, Request $request) {
68+
return in_array($key, [
69+
'current_password',
70+
'password',
71+
'password_confirmation',
72+
]);
73+
});
74+
```
75+
6476
## Configuration
6577

6678
### File uploads

src/Middleware/XssCleanInput.php

Lines changed: 57 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,19 @@ class XssCleanInput extends TransformsRequest
3232
*/
3333
protected static $skipCallbacks = [];
3434

35+
/**
36+
* All of the registered skip keys callbacks.
37+
*
38+
* @var array
39+
*/
40+
protected static $skipKeyCallbacks = [];
41+
3542
/**
3643
* The attributes that should not be cleaned.
3744
*
3845
* @var array
3946
*/
40-
protected $exceptKeys = [
41-
//
42-
];
47+
protected $exceptKeys = [];
4348

4449
/**
4550
* Array of sanitized keys.
@@ -48,6 +53,13 @@ class XssCleanInput extends TransformsRequest
4853
*/
4954
protected $sanitizedKeys = [];
5055

56+
/**
57+
* Original request.
58+
*
59+
* @var \Illuminate\Http\Request
60+
*/
61+
protected $originalRequest;
62+
5163
/**
5264
* Create a new instance.
5365
*
@@ -79,16 +91,20 @@ public function handle($request, Closure $next)
7991
}
8092
}
8193

82-
$originalRequest = clone $request;
94+
$dispatchEvent = $this->enabledInConfig('dispatch_event_on_malicious_input');
95+
96+
if (count(static::$skipKeyCallbacks) > 0 || $dispatchEvent) {
97+
$this->originalRequest = clone $request;
98+
}
8399

84100
$this->clean($request);
85101

86102
if (count($this->sanitizedKeys) === 0) {
87103
return $next($request);
88104
}
89105

90-
if ($this->enabledInConfig('dispatch_event_on_malicious_input')) {
91-
event(new MaliciousInputFound($this->sanitizedKeys, $originalRequest, $request));
106+
if ($dispatchEvent) {
107+
event(new MaliciousInputFound($this->sanitizedKeys, $this->originalRequest, $request));
92108
}
93109

94110
if ($this->enabledInConfig('terminate_request_on_malicious_input')) {
@@ -111,6 +127,12 @@ protected function transform($key, $value)
111127
return $value;
112128
}
113129

130+
foreach (static::$skipKeyCallbacks as $callback) {
131+
if ($callback($key, $value, $this->originalRequest)) {
132+
return $value;
133+
}
134+
}
135+
114136
if ($value === null || is_bool($value) || is_int($value) || is_float($value)) {
115137
return $value;
116138
}
@@ -140,6 +162,12 @@ protected function transform($key, $value)
140162
return $this->enabledInConfig('completely_replace_malicious_input') ? null : $output;
141163
}
142164

165+
/**
166+
* Returns a boolean whether an option has been enabled.
167+
*
168+
* @param string $key
169+
* @return boolean
170+
*/
143171
private function enabledInConfig($key): bool
144172
{
145173
return (bool) config("xss-protection.middleware.{$key}");
@@ -155,4 +183,27 @@ public static function skipWhen(Closure $callback)
155183
{
156184
static::$skipCallbacks[] = $callback;
157185
}
186+
187+
/**
188+
* Register a callback that instructs the middleware to be skipped.
189+
*
190+
* @param \Closure $callback
191+
* @return void
192+
*/
193+
public static function skipKeyWhen(Closure $callback)
194+
{
195+
static::$skipKeyCallbacks[] = $callback;
196+
}
197+
198+
/**
199+
* Clear static callback arrays.
200+
*
201+
* @return void
202+
*/
203+
public static function clearCallbacks()
204+
{
205+
static::$skipCallbacks = [];
206+
207+
static::$skipKeyCallbacks = [];
208+
}
158209
}

tests/MiddlewareTest.php

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
use ProtoneMedia\LaravelXssProtection\Middleware\XssCleanInput;
88
use Symfony\Component\HttpKernel\Exception\HttpException;
99

10+
beforeEach(function () {
11+
XssCleanInput::clearCallbacks();
12+
});
13+
1014
it('can partly replace the malicious input', function () {
1115
$request = Request::createFromGlobals()->merge([
1216
'key' => 'test<script>script</script>',
@@ -42,19 +46,14 @@
4246
});
4347

4448
it('can add a callback to skip a request', function () {
45-
class SkipXssCleanInput extends XssCleanInput
46-
{
47-
protected static $skipCallbacks = [];
48-
}
49-
50-
SkipXssCleanInput::skipWhen(fn () => true);
49+
XssCleanInput::skipWhen(fn () => true);
5150

5251
$request = Request::createFromGlobals()->merge([
5352
'key' => 'test<script>script</script>',
5453
]);
5554

56-
/** @var SkipXssCleanInput $middleware */
57-
$middleware = app(SkipXssCleanInput::class);
55+
/** @var XssCleanInput $middleware */
56+
$middleware = app(XssCleanInput::class);
5857
$middleware->handle($request, fn ($request) => $request);
5958

6059
expect($request->input('key'))->toBe('test<script>script</script>');
@@ -189,3 +188,32 @@ class ExceptXssCleanInput extends XssCleanInput
189188
expect($request->input('e'))->toBe('e');
190189
expect($request->input('f'))->toBe('f');
191190
});
191+
192+
it('can skip a key by a callback', function () {
193+
XssCleanInput::skipKeyWhen(function (string $key, $value, $request) {
194+
expect($request)->toBeInstanceOf(Request::class);
195+
expect($value)->toBe('test<script>script</script>');
196+
197+
return in_array($key, ['allow', 'nested.allowed']);
198+
});
199+
200+
$request = Request::createFromGlobals()->merge([
201+
'key' => 'test<script>script</script>',
202+
'allow' => 'test<script>script</script>',
203+
204+
'nested' => [
205+
'key' => 'test<script>script</script>',
206+
'allowed' => 'test<script>script</script>',
207+
],
208+
]);
209+
210+
/** @var XssCleanInput $middleware */
211+
$middleware = app(XssCleanInput::class);
212+
$middleware->handle($request, fn ($request) => $request);
213+
214+
expect($request->input('key'))->toBeNull();
215+
expect($request->input('nested.key'))->toBeNull();
216+
217+
expect($request->input('allow'))->toBe('test<script>script</script>');
218+
expect($request->input('nested.allowed'))->toBe('test<script>script</script>');
219+
});

0 commit comments

Comments
 (0)