2021-11-04 16:28:41 +01:00
|
|
|
<?php
|
|
|
|
App::uses('AppModel', 'Model');
|
|
|
|
App::uses('JsonTool', 'Tools');
|
2021-11-05 16:50:19 +01:00
|
|
|
App::uses('BetterSecurity', 'Tools');
|
2021-11-04 16:28:41 +01:00
|
|
|
|
2021-11-05 10:49:16 +01:00
|
|
|
/**
|
|
|
|
* Class for ondemand encryption of JSON serialized value
|
|
|
|
*/
|
|
|
|
class EncryptedValue implements JsonSerializable
|
|
|
|
{
|
|
|
|
const ENCRYPTED_MAGIC = "\x1F\x1D";
|
|
|
|
|
|
|
|
/** @var string */
|
|
|
|
private $value;
|
|
|
|
|
|
|
|
public function __construct($value)
|
|
|
|
{
|
|
|
|
$this->value = $value;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return mixed
|
|
|
|
* @throws JsonException
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function decrypt()
|
|
|
|
{
|
2021-11-05 16:50:19 +01:00
|
|
|
$decrypt = BetterSecurity::decrypt($this->value, Configure::read('Security.encryption_key'));
|
2021-11-05 10:49:16 +01:00
|
|
|
return JsonTool::decode($decrypt);
|
|
|
|
}
|
|
|
|
|
|
|
|
public function __toString()
|
|
|
|
{
|
|
|
|
return $this->decrypt();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function jsonSerialize()
|
|
|
|
{
|
|
|
|
return $this->decrypt();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-04 16:28:41 +01:00
|
|
|
class SystemSetting extends AppModel
|
|
|
|
{
|
|
|
|
public $actsAs = [
|
|
|
|
'AuditLog'
|
|
|
|
];
|
|
|
|
|
|
|
|
public $primaryKey = 'setting';
|
|
|
|
|
2021-11-06 21:21:40 +01:00
|
|
|
// Blocked setting that cannot be saved or fetched from DB. The same as cli_only settings.
|
|
|
|
const BLOCKED_SETTINGS = [
|
|
|
|
'Security.encryption_key',
|
|
|
|
'Security.disable_local_feed_access',
|
|
|
|
'GnuPG.binary',
|
|
|
|
'MISP.python_bin',
|
|
|
|
'MISP.ca_path',
|
|
|
|
'MISP.tmpdir',
|
|
|
|
'MISP.system_setting_db',
|
|
|
|
'MISP.attachments_dir',
|
|
|
|
];
|
|
|
|
|
|
|
|
// Allow to set config values just for these categories
|
|
|
|
const ALLOWED_CATEGORIES = [
|
|
|
|
'MISP',
|
|
|
|
'Security',
|
|
|
|
'GnuPG',
|
|
|
|
'SMIME',
|
|
|
|
'Proxy',
|
|
|
|
'SecureAuth',
|
|
|
|
'Session',
|
|
|
|
'Plugin',
|
|
|
|
'debug',
|
|
|
|
'site_admin_debug',
|
|
|
|
];
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set config values from database into global Configure class
|
|
|
|
*/
|
|
|
|
public static function setGlobalSetting()
|
|
|
|
{
|
|
|
|
$systemSetting = ClassRegistry::init('SystemSetting');
|
|
|
|
$settings = $systemSetting->getSettings();
|
|
|
|
foreach ($settings as $settingName => $settingValue) {
|
|
|
|
$firstPart = explode('.', $settingName)[0];
|
|
|
|
if (in_array($firstPart, self::ALLOWED_CATEGORIES, true) && !in_array($settingName, self::BLOCKED_SETTINGS, true)) {
|
|
|
|
Configure::write($settingName, $settingValue);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-11-05 10:49:16 +01:00
|
|
|
/**
|
|
|
|
* @return array
|
|
|
|
*/
|
2021-11-04 16:28:41 +01:00
|
|
|
public function getSettings()
|
|
|
|
{
|
|
|
|
$settings = $this->find('list', [
|
|
|
|
'fields' => ['SystemSetting.setting', 'SystemSetting.value'],
|
|
|
|
]);
|
2021-11-05 10:49:16 +01:00
|
|
|
return array_map([$this, 'decode'], $settings);
|
2021-11-04 16:28:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2021-11-05 10:49:16 +01:00
|
|
|
* @param string $setting Setting name
|
2021-11-04 16:28:41 +01:00
|
|
|
* @param mixed $value
|
|
|
|
* @throws Exception
|
|
|
|
*/
|
|
|
|
public function setSetting($setting, $value)
|
|
|
|
{
|
2021-11-06 21:21:40 +01:00
|
|
|
$firstPart = explode('.', $setting)[0];
|
|
|
|
if (!in_array($firstPart, self::ALLOWED_CATEGORIES, true) || in_array($setting, self::BLOCKED_SETTINGS, true)) {
|
|
|
|
return false; // blocked setting
|
|
|
|
}
|
|
|
|
|
2021-11-06 22:12:48 +01:00
|
|
|
if ($value === '' || $value === null) {
|
|
|
|
if ($this->hasAny(['SystemSetting.setting' => $setting])) {
|
|
|
|
return $this->delete($setting); // delete the whole setting when value is empty
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2021-11-05 10:49:16 +01:00
|
|
|
$value = JsonTool::encode($value);
|
|
|
|
|
|
|
|
// If encryption is enabled and setting name contains `password` or `apikey` string, encrypt value to protect it
|
|
|
|
$key = Configure::read('Security.encryption_key');
|
2021-11-05 21:30:17 +01:00
|
|
|
if ($key && self::isSensitive($setting)) {
|
2021-11-05 16:50:19 +01:00
|
|
|
$value = EncryptedValue::ENCRYPTED_MAGIC . BetterSecurity::encrypt($value, $key);
|
2021-11-05 10:49:16 +01:00
|
|
|
}
|
|
|
|
|
2021-11-04 16:28:41 +01:00
|
|
|
$valid = $this->save(['SystemSetting' => [
|
|
|
|
'setting' => $setting,
|
2021-11-05 10:49:16 +01:00
|
|
|
'value' => $value,
|
2021-11-04 16:28:41 +01:00
|
|
|
]]);
|
|
|
|
if (!$valid) {
|
|
|
|
throw new Exception("Could not save system setting `$setting` because of validation errors: " . JsonTool::encode($this->validationErrors));
|
|
|
|
}
|
2021-11-06 21:21:40 +01:00
|
|
|
return true;
|
2021-11-04 16:28:41 +01:00
|
|
|
}
|
2021-11-05 10:49:16 +01:00
|
|
|
|
2021-11-05 21:51:31 +01:00
|
|
|
/**
|
|
|
|
* @param string|null $old Old (or current) encryption key.
|
|
|
|
* @param string|null $new New encryption key. If empty, encrypted values will be decrypted.
|
|
|
|
* @throws JsonException
|
|
|
|
*/
|
|
|
|
public function reencrypt($old, $new)
|
|
|
|
{
|
|
|
|
$settings = $this->find('list', [
|
|
|
|
'fields' => ['SystemSetting.setting', 'SystemSetting.value'],
|
|
|
|
]);
|
|
|
|
$toSave = [];
|
|
|
|
foreach ($settings as $setting => $value) {
|
|
|
|
if (!self::isSensitive($setting)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (substr($value, 0, 2) === EncryptedValue::ENCRYPTED_MAGIC) {
|
|
|
|
$value = BetterSecurity::decrypt(substr($value, 2), $old);
|
|
|
|
}
|
|
|
|
if (!empty($new)) {
|
|
|
|
$value = EncryptedValue::ENCRYPTED_MAGIC . BetterSecurity::encrypt($value, $new);
|
|
|
|
}
|
|
|
|
$toSave[] = ['SystemSetting' => [
|
|
|
|
'setting' => $setting,
|
|
|
|
'value' => $value,
|
|
|
|
]];
|
|
|
|
}
|
|
|
|
return $this->saveMany($toSave);
|
|
|
|
}
|
|
|
|
|
2021-11-05 10:49:16 +01:00
|
|
|
/**
|
|
|
|
* @param string $value
|
|
|
|
* @return EncryptedValue|mixed
|
|
|
|
* @throws JsonException
|
|
|
|
*/
|
|
|
|
private function decode($value)
|
|
|
|
{
|
|
|
|
if (substr($value, 0, 2) === EncryptedValue::ENCRYPTED_MAGIC) {
|
|
|
|
return new EncryptedValue(substr($value, 2));
|
|
|
|
} else {
|
|
|
|
return JsonTool::decode($value);
|
|
|
|
}
|
|
|
|
}
|
2021-11-05 21:30:17 +01:00
|
|
|
|
|
|
|
/**
|
2021-11-05 22:26:12 +01:00
|
|
|
* Sensitive setting are passwords or api keys.
|
2021-11-06 01:08:20 +01:00
|
|
|
* @param string $setting Setting name
|
2021-11-05 21:30:17 +01:00
|
|
|
* @return bool
|
|
|
|
*/
|
|
|
|
public static function isSensitive($setting)
|
|
|
|
{
|
2021-11-06 01:08:20 +01:00
|
|
|
if ($setting === 'Security.encryption_key' || $setting === 'Security.salt') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (substr($setting, 0, 7) === 'Plugin.' && (strpos($setting, 'apikey') !== false || strpos($setting, 'secret') !== false)) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return strpos($setting, 'password') !== false;
|
2021-11-05 21:30:17 +01:00
|
|
|
}
|
2021-11-04 16:28:41 +01:00
|
|
|
}
|