From 6cb2c8b2d9a4e954388ffb25b42c225da2a2a4be Mon Sep 17 00:00:00 2001 From: Kairat Jenishev Date: Tue, 10 Dec 2024 23:44:41 +0600 Subject: [PATCH] An ability to have wildcards in `yii\log\Target::$maskVars` array #20295 --- framework/CHANGELOG.md | 1 + framework/log/Target.php | 58 ++++++++++++++++++++++++++++-- tests/framework/log/TargetTest.php | 39 ++++++++++++++++++++ 3 files changed, 95 insertions(+), 3 deletions(-) diff --git a/framework/CHANGELOG.md b/framework/CHANGELOG.md index 60cc9c34596..18ac40550cc 100644 --- a/framework/CHANGELOG.md +++ b/framework/CHANGELOG.md @@ -22,6 +22,7 @@ Yii Framework 2 Change Log - Bug #20140: Fix compatibility with PHP 8.4: calling `session_set_save_handler()` (Izumi-kun) - New #20185: Add `BackedEnum` support to `AttributeTypecastBehavior` (briedis) - Bug #17365: Fix "Trying to access array offset on null" warning (xcopy) +- Enh #20295: An ability to have wildcards in `yii\log\Target::$maskVars` array (xcopy) 2.0.51 July 18, 2024 -------------------- diff --git a/framework/log/Target.php b/framework/log/Target.php index b2335949dc8..8e87160c496 100644 --- a/framework/log/Target.php +++ b/framework/log/Target.php @@ -11,6 +11,7 @@ use yii\base\Component; use yii\base\InvalidConfigException; use yii\helpers\ArrayHelper; +use yii\helpers\StringHelper; use yii\helpers\VarDumper; use yii\web\Request; @@ -161,6 +162,54 @@ public function collect($messages, $final) } } + /** + * 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. * The default implementation will dump user information, system variables, etc. @@ -169,9 +218,12 @@ public function collect($messages, $final) protected function getContextMessage() { $context = ArrayHelper::filter($GLOBALS, $this->logVars); + $items = self::flatten($context); foreach ($this->maskVars as $var) { - if (ArrayHelper::getValue($context, $var) !== null) { - ArrayHelper::setValue($context, $var, '***'); + foreach ($items as $key => $value) { + if (StringHelper::matchWildcard($var, $key, ['caseSensitive' => false])) { + ArrayHelper::setValue($context, $key, '***'); + } } } $result = []; @@ -292,7 +344,7 @@ public static function filterMessages($messages, $levels = 0, $categories = [], */ public function formatMessage($message) { - list($text, $level, $category, $timestamp) = $message; + [$text, $level, $category, $timestamp] = $message; $level = Logger::getLevelName($level); if (!is_string($text)) { // exceptions may not be serializable if in the call stack somewhere is a Closure diff --git a/tests/framework/log/TargetTest.php b/tests/framework/log/TargetTest.php index 7548092b788..06eb74d38fe 100644 --- a/tests/framework/log/TargetTest.php +++ b/tests/framework/log/TargetTest.php @@ -345,6 +345,45 @@ public function testFlushingWithProfilingEnabledAndOverflow() $logger->log('token.b', 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