Fix #20295: Add an ability to have wildcards in yii\log\Target::$maskVars array

This commit is contained in:
Kairat Jenishev
2025-01-02 01:43:47 +06:00
committed by GitHub
parent a8586fc296
commit 3fc7e71c67
5 changed files with 126 additions and 6 deletions

View File

@@ -173,6 +173,23 @@ return [
При задании значением свойства `logVars` пустого массива, общая информация не будет выводиться. При задании значением свойства `logVars` пустого массива, общая информация не будет выводиться.
Для определения собственного алгоритма подключения общей информации, следует переопределить метод [[yii\log\Target::getContextMessage()]]. Для определения собственного алгоритма подключения общей информации, следует переопределить метод [[yii\log\Target::getContextMessage()]].
Если некоторые из полей вашего запроса содержат конфиденциальную информацию, которую вы не хотели бы логировать (например, пароли, токены доступа),
вы можете дополнительно настроить свойство `maskVars`, которое может содержать как точные значения, так и шаблоны (без учета регистра).
По умолчанию следующие параметры запроса будут замаскированы с помощью `***`:
`$_SERVER[HTTP_AUTHORIZATION]`, `$_SERVER[PHP_AUTH_USER]`, `$_SERVER[PHP_AUTH_PW]`, но вы можете задать свои собственные. Например:
```php
[
'class' => 'yii\log\FileTarget',
'logVars' => ['_SERVER'],
'maskVars' => [
'_SERVER.HTTP_X_PASSWORD',
'_SERVER.*_SECRET', // соответствует всем, заканчивающимся на "_SECRET"
'_SERVER.SECRET_*', // соответствует всем, начинающимся с "SECRET_"
'_SERVER.*SECRET*', // соответствует всем содержащим "SECRET"
]
]
```
### Уровень отслеживания выполнения кода <span id="trace-level"></span> ### Уровень отслеживания выполнения кода <span id="trace-level"></span>

View File

@@ -217,14 +217,20 @@ Or if you want to implement your own way of providing context information, you m
[[yii\log\Target::getContextMessage()]] method. [[yii\log\Target::getContextMessage()]] method.
In case some of your request fields contain sensitive information you would not like to log (e.g. passwords, access tokens), In case some of your request fields contain sensitive information you would not like to log (e.g. passwords, access tokens),
you may additionally configure `maskVars` property. By default, the following request parameters will be masked with `***`: you may additionally configure `maskVars` property, which can contain both exact values and (case-insensitive) patterns. By default,
`$_SERVER[HTTP_AUTHORIZATION]`, `$_SERVER[PHP_AUTH_USER]`, `$_SERVER[PHP_AUTH_PW]`, but you can set your own: the following request parameters will be masked with `***`:
`$_SERVER[HTTP_AUTHORIZATION]`, `$_SERVER[PHP_AUTH_USER]`, `$_SERVER[PHP_AUTH_PW]`, but you can set your own. For example:
```php ```php
[ [
'class' => 'yii\log\FileTarget', 'class' => 'yii\log\FileTarget',
'logVars' => ['_SERVER'], 'logVars' => ['_SERVER'],
'maskVars' => ['_SERVER.HTTP_X_PASSWORD'] 'maskVars' => [
'_SERVER.HTTP_X_PASSWORD',
'_SERVER.*_SECRET', // matches all ending with "_SECRET"
'_SERVER.SECRET_*', // matches all starting with "SECRET_"
'_SERVER.*SECRET*', // matches all containing "SECRET"
]
] ]
``` ```

View File

@@ -22,6 +22,7 @@ Yii Framework 2 Change Log
- Bug #20140: Fix compatibility with PHP 8.4: calling `session_set_save_handler()` (Izumi-kun) - Bug #20140: Fix compatibility with PHP 8.4: calling `session_set_save_handler()` (Izumi-kun)
- New #20185: Add `BackedEnum` support to `AttributeTypecastBehavior` (briedis) - New #20185: Add `BackedEnum` support to `AttributeTypecastBehavior` (briedis)
- Bug #17365: Fix "Trying to access array offset on null" warning (xcopy) - Bug #17365: Fix "Trying to access array offset on null" warning (xcopy)
- Enh #20295: Add an ability to have wildcards in `yii\log\Target::$maskVars` array (xcopy)
- Bug #20296: Fix broken enum test (briedis) - Bug #20296: Fix broken enum test (briedis)
- Bug #20300: Clear stat cache in `FileCache::setValue()` (rob006) - Bug #20300: Clear stat cache in `FileCache::setValue()` (rob006)

View File

@@ -11,6 +11,7 @@ use Yii;
use yii\base\Component; use yii\base\Component;
use yii\base\InvalidConfigException; use yii\base\InvalidConfigException;
use yii\helpers\ArrayHelper; use yii\helpers\ArrayHelper;
use yii\helpers\StringHelper;
use yii\helpers\VarDumper; use yii\helpers\VarDumper;
use yii\web\Request; use yii\web\Request;
@@ -91,6 +92,11 @@ abstract class Target extends Component
* - `var` - `var` will be logged as `***` * - `var` - `var` will be logged as `***`
* - `var.key` - only `var[key]` will be logged as `***` * - `var.key` - only `var[key]` will be logged as `***`
* *
* In addition, this property accepts (case-insensitive) patterns. For example:
* - `_SERVER.*_SECRET` matches all ending with `_SECRET`, such as `$_SERVER['TOKEN_SECRET']` etc.
* - `_SERVER.SECRET_*` matches all starting with `SECRET_`, such as `$_SERVER['SECRET_TOKEN']` etc.
* - `_SERVER.*SECRET*` matches all containing `SECRET` i.e. both of the above.
*
* @since 2.0.16 * @since 2.0.16
*/ */
public $maskVars = [ public $maskVars = [
@@ -161,6 +167,54 @@ abstract class Target extends Component
} }
} }
/**
* Flattens a multidimensional array into a one-dimensional array.
*
* This method recursively traverses the input array and concatenates the keys
* to form a new key in the resulting array.
*
* Example:
*
* ```php
* $array = [
* 'A' => [1, 2],
* 'B' => [
* 'C' => 1,
* 'D' => 2,
* ],
* 'E' => 1,
* ];
* $result = \yii\log\Target::flatten($array);
* // result will be:
* // [
* // 'A.0' => 1
* // 'A.1' => 2
* // 'B.C' => 1
* // 'B.D' => 2
* // 'E' => 1
* // ]
* ```
*
* @param array $array the input array to be flattened.
* @param string $prefix the prefix to be added to each key in the resulting array.
*
* @return array the flattened array.
*/
private static function flatten($array, $prefix = ''): array
{
$result = [];
foreach ($array as $key => $value) {
if (is_array($value)) {
$result = array_merge($result, self::flatten($value, $prefix . $key . '.'));
} else {
$result[$prefix . $key] = $value;
}
}
return $result;
}
/** /**
* Generates the context information to be logged. * Generates the context information to be logged.
* The default implementation will dump user information, system variables, etc. * The default implementation will dump user information, system variables, etc.
@@ -169,9 +223,12 @@ abstract class Target extends Component
protected function getContextMessage() protected function getContextMessage()
{ {
$context = ArrayHelper::filter($GLOBALS, $this->logVars); $context = ArrayHelper::filter($GLOBALS, $this->logVars);
$items = self::flatten($context);
foreach ($this->maskVars as $var) { foreach ($this->maskVars as $var) {
if (ArrayHelper::getValue($context, $var) !== null) { foreach ($items as $key => $value) {
ArrayHelper::setValue($context, $var, '***'); if (StringHelper::matchWildcard($var, $key, ['caseSensitive' => false])) {
ArrayHelper::setValue($context, $key, '***');
}
} }
} }
$result = []; $result = [];
@@ -292,7 +349,7 @@ abstract class Target extends Component
*/ */
public function formatMessage($message) public function formatMessage($message)
{ {
list($text, $level, $category, $timestamp) = $message; [$text, $level, $category, $timestamp] = $message;
$level = Logger::getLevelName($level); $level = Logger::getLevelName($level);
if (!is_string($text)) { if (!is_string($text)) {
// exceptions may not be serializable if in the call stack somewhere is a Closure // exceptions may not be serializable if in the call stack somewhere is a Closure

View File

@@ -345,6 +345,45 @@ class TargetTest extends TestCase
$logger->log('token.b', Logger::LEVEL_PROFILE_END, 'category'); $logger->log('token.b', Logger::LEVEL_PROFILE_END, 'category');
$logger->log('token.a', Logger::LEVEL_PROFILE_END, 'category'); $logger->log('token.a', Logger::LEVEL_PROFILE_END, 'category');
} }
public function testWildcardsInMaskVars()
{
$keys = [
'PASSWORD',
'password',
'password_repeat',
'repeat_password',
'repeat_password_again',
'1password',
'password1',
];
$password = '!P@$$w0rd#';
$items = array_fill_keys($keys, $password);
$GLOBALS['_TEST'] = array_merge(
$items,
['a' => $items],
['b' => ['c' => $items]],
['d' => ['e' => ['f' => $items]]],
);
$target = new TestTarget([
'logVars' => ['_SERVER', '_TEST'],
'maskVars' => [
// option 1: exact value(s)
'_SERVER.DOCUMENT_ROOT',
// option 2: pattern(s)
'_TEST.*password*',
]
]);
$message = $target->getContextMessage();
$this->assertStringContainsString("'DOCUMENT_ROOT' => '***'", $message);
$this->assertStringNotContainsString($password, $message);
}
} }
class TestTarget extends Target class TestTarget extends Target