Skip to content

Commit bf25256

Browse files
authored
Validate and sanitize JSON system settings (#8199)
Add centralized JSON validation and sanitization for system settings. Moves JSON helpers into , validates JSON inputs via , sanitizes string values with , and ensures delegates to so API and admin UI updates are validated.
1 parent f63ea6c commit bf25256

File tree

2 files changed

+77
-2
lines changed

2 files changed

+77
-2
lines changed

src/ChurchCRM/dto/SystemConfig.php

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use ChurchCRM\Config;
66
use ChurchCRM\data\Countries;
77
use ChurchCRM\model\ChurchCRM\ListOptionQuery;
8+
use ChurchCRM\Utils\InputUtils;
89
use Exception;
910
use Monolog\Level;
1011
use Monolog\Logger;
@@ -418,15 +419,38 @@ public static function setValue(string $name, $value): void
418419
throw new \Exception(gettext('An invalid configuration name has been requested') . ': ' . $name);
419420
}
420421

421-
self::$configs[$name]->setValue($value);
422+
$configItem = self::$configs[$name];
423+
424+
// If this config item is declared as JSON, validate and sanitize it before saving
425+
if ($configItem->getType() === 'json') {
426+
try {
427+
$decoded = InputUtils::validateJson($value);
428+
} catch (\InvalidArgumentException $e) {
429+
throw new \Exception(gettext('Invalid JSON provided for configuration') . ': ' . $name . ' - ' . $e->getMessage());
430+
}
431+
432+
// Recursively sanitize any string values to reduce XSS risk
433+
$sanitized = InputUtils::sanitizeJsonStrings($decoded);
434+
435+
try {
436+
$value = json_encode($sanitized, JSON_THROW_ON_ERROR);
437+
} catch (\JsonException $e) {
438+
throw new \Exception(gettext('Unable to re-encode JSON for configuration') . ': ' . $name . ' - ' . $e->getMessage());
439+
}
440+
}
441+
442+
$configItem->setValue($value);
422443
}
423444

445+
446+
424447
public static function setValueById(string $Id, $value): void
425448
{
426449
$success = false;
427450
foreach (self::$configs as $configItem) {
428451
if ($configItem->getId() == $Id) {
429-
$configItem->setValue($value);
452+
// Delegate to setValue by name so JSON validation/sanitization is applied
453+
self::setValue($configItem->getName(), $value);
430454
$success = true;
431455
}
432456
}

src/ChurchCRM/utils/InputUtils.php

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,57 @@ public static function sanitizeHTML($sInput): string
9292
return $purifier->purify($sInput);
9393
}
9494

95+
/**
96+
* Recursively sanitize JSON string values by stripping HTML tags.
97+
* Use this for JSON configuration payloads where values should not contain HTML.
98+
*
99+
* @param mixed $data
100+
* @return mixed
101+
*/
102+
public static function sanitizeJsonStrings($data)
103+
{
104+
if (is_array($data)) {
105+
$out = [];
106+
foreach ($data as $k => $v) {
107+
$out[$k] = self::sanitizeJsonStrings($v);
108+
}
109+
110+
return $out;
111+
}
112+
113+
if (is_string($data)) {
114+
return self::sanitizeText($data);
115+
}
116+
117+
return $data;
118+
}
119+
120+
/**
121+
* Validate JSON input and return decoded array/object.
122+
* Accepts either a JSON string or already-decoded array. Throws on invalid JSON.
123+
*
124+
* @param mixed $json
125+
* @return mixed
126+
* @throws \InvalidArgumentException
127+
*/
128+
public static function validateJson($json)
129+
{
130+
if (is_array($json) || is_object($json)) {
131+
return $json;
132+
}
133+
134+
if (is_string($json)) {
135+
$trimmed = trim($json);
136+
try {
137+
return json_decode($trimmed, true, 512, JSON_THROW_ON_ERROR);
138+
} catch (\JsonException $e) {
139+
throw new \InvalidArgumentException('Invalid JSON: ' . $e->getMessage());
140+
}
141+
}
142+
143+
throw new \InvalidArgumentException('Invalid JSON input type');
144+
}
145+
95146
/**
96147
* Escape HTML for safe display in HTML context (body content and attributes)
97148
* Converts special characters to HTML entities: &, <, >, ", '

0 commit comments

Comments
 (0)