|
9 | 9 | use Illuminate\Contracts\Validation\ValidatesWhenResolved; |
10 | 10 | use Illuminate\Contracts\Validation\Validator; |
11 | 11 | use Illuminate\Foundation\Http\Attributes\ErrorBag; |
| 12 | +use Illuminate\Foundation\Http\Attributes\FailOnUnknownFields; |
12 | 13 | use Illuminate\Foundation\Http\Attributes\RedirectTo; |
13 | 14 | use Illuminate\Foundation\Http\Attributes\RedirectToRoute; |
14 | 15 | use Illuminate\Foundation\Http\Attributes\StopOnFirstFailure; |
15 | 16 | use Illuminate\Http\Request; |
16 | 17 | use Illuminate\Routing\Redirector; |
| 18 | +use Illuminate\Support\Arr; |
17 | 19 | use Illuminate\Validation\ValidatesWhenResolvedTrait; |
18 | 20 | use ReflectionClass; |
19 | 21 |
|
@@ -77,6 +79,13 @@ class FormRequest extends Request implements ValidatesWhenResolved |
77 | 79 | */ |
78 | 80 | protected $validator; |
79 | 81 |
|
| 82 | + /** |
| 83 | + * Indicates if unknown fields should be rejected for all form requests. |
| 84 | + * |
| 85 | + * @var bool |
| 86 | + */ |
| 87 | + protected static bool $globalFailOnUnknownFields = false; |
| 88 | + |
80 | 89 | /** |
81 | 90 | * Get the validator instance for the request. |
82 | 91 | * |
@@ -109,6 +118,12 @@ protected function getValidatorInstance() |
109 | 118 | )); |
110 | 119 | } |
111 | 120 |
|
| 121 | + if ($this->shouldFailOnUnknownFields()) { |
| 122 | + $validator->after(function (Validator $validator) { |
| 123 | + $this->validateNoUnknownFields($validator); |
| 124 | + }); |
| 125 | + } |
| 126 | + |
112 | 127 | $this->setValidator($validator); |
113 | 128 |
|
114 | 129 | return $this->validator; |
@@ -144,6 +159,7 @@ protected function configureFromAttributes() |
144 | 159 | if (count($errorBag) > 0) { |
145 | 160 | $this->errorBag = $errorBag[0]->newInstance()->name; |
146 | 161 | } |
| 162 | + |
147 | 163 | } |
148 | 164 |
|
149 | 165 | /** |
@@ -192,6 +208,66 @@ protected function validationRules() |
192 | 208 | return method_exists($this, 'rules') ? $this->container->call([$this, 'rules']) : []; |
193 | 209 | } |
194 | 210 |
|
| 211 | + /** |
| 212 | + * Determine if fields not present in rules should fail validation. |
| 213 | + * |
| 214 | + * @return bool |
| 215 | + */ |
| 216 | + protected function shouldFailOnUnknownFields(): bool |
| 217 | + { |
| 218 | + $failOnUnknownFields = (new ReflectionClass($this))->getAttributes(FailOnUnknownFields::class); |
| 219 | + |
| 220 | + return $failOnUnknownFields !== [] |
| 221 | + ? $failOnUnknownFields[0]->newInstance()->value |
| 222 | + : static::$globalFailOnUnknownFields; |
| 223 | + } |
| 224 | + |
| 225 | + /** |
| 226 | + * Validate that no unknown fields were sent as input. |
| 227 | + * |
| 228 | + * @param \Illuminate\Contracts\Validation\Validator $validator |
| 229 | + * @return void |
| 230 | + */ |
| 231 | + protected function validateNoUnknownFields(Validator $validator): void |
| 232 | + { |
| 233 | + $allowedKeys = array_keys($this->validationRules()); |
| 234 | + |
| 235 | + foreach (array_keys(Arr::dot($this->all())) as $inputKey) { |
| 236 | + if (! $this->isKnownField($inputKey, $allowedKeys)) { |
| 237 | + $validator->errors()->add($inputKey, trans('validation.prohibited', [ |
| 238 | + 'attribute' => str_replace('_', ' ', $inputKey), |
| 239 | + ])); |
| 240 | + } |
| 241 | + } |
| 242 | + } |
| 243 | + |
| 244 | + /** |
| 245 | + * Determine if the given input key is an allowed key based on the validation rules. |
| 246 | + * |
| 247 | + * @param string $inputKey |
| 248 | + * @param array $allowedKeys |
| 249 | + * @return bool |
| 250 | + */ |
| 251 | + protected function isKnownField(string $inputKey, array $allowedKeys): bool |
| 252 | + { |
| 253 | + foreach ($allowedKeys as $ruleKey) { |
| 254 | + if ($ruleKey === $inputKey) { |
| 255 | + return true; |
| 256 | + } |
| 257 | + |
| 258 | + if (str_contains($ruleKey, '*')) { |
| 259 | + $pattern = '/^'.str_replace('\*', '[^.]+', preg_quote($ruleKey, '/')).'$/'; |
| 260 | + |
| 261 | + if (preg_match($pattern, $inputKey)) { |
| 262 | + return true; |
| 263 | + } |
| 264 | + } |
| 265 | + |
| 266 | + } |
| 267 | + |
| 268 | + return false; |
| 269 | + } |
| 270 | + |
195 | 271 | /** |
196 | 272 | * Handle a failed validation attempt. |
197 | 273 | * |
@@ -304,6 +380,17 @@ public function attributes() |
304 | 380 | return []; |
305 | 381 | } |
306 | 382 |
|
| 383 | + /** |
| 384 | + * Enable or disable unknown-field rejection globally for all form requests. |
| 385 | + * |
| 386 | + * @param bool $value |
| 387 | + * @return void |
| 388 | + */ |
| 389 | + public static function failOnUnknownFields(bool $value = true): void |
| 390 | + { |
| 391 | + static::$globalFailOnUnknownFields = $value; |
| 392 | + } |
| 393 | + |
307 | 394 | /** |
308 | 395 | * Set the Validator instance. |
309 | 396 | * |
|
0 commit comments