0

I need to make path for yii2 logging. This path should avoid logging variables like password, etc. According to documentation (yii\log\Target) for excluding variable I need to configure logVars.

In config:

 'targets' => [
            [
                'class' => yii\log\FileTarget::className(),
                'levels' => ['error', 'warning'],
                'maxLogFiles' => 1000,
                'maxFileSize' => 102400,
                'logVars' => '!_POST[LoginForm[password]]',
            ],

But LoginForm password still appear in log files. How to configure logVars properly.

P.S. I have tried !LoginForm.password, !_POST.LoginForm.password

2 Answers 2

5

I prepared the code for the same purpose - maybe you will find it useful.

Override getContextMessage() in chosen target class like:

<?php

namespace your\namespace\here;

use yii\helpers\ArrayHelper;

class FileTarget extends \yii\log\FileTarget
{
    protected function getContextMessage()
    {
        $context = ArrayHelper::filter($GLOBALS, $this->logVars);
        $result = [];
        foreach ($context as $key => $value) {
            if (\is_string($value) && stripos($key, 'password') !== false) {
                $result[] = "\${$key} = '** PASSWORD HIDDEN **'";
            } else {
                $result[] = "\${$key} = " . \your\namespace\here\LogVarDumper::dumpAsString($value);
            }
        }
        return implode("\n\n", $result);
    }
}

Override VarDumper class with LogVarDumper (used above):

<?php

namespace your\namespace\here;

use yii\base\InvalidValueException;

/**
 * Class LogVarDumper
 * Extended to handle logs password fields darkening.
 */
class LogVarDumper extends \yii\helpers\VarDumper
{
    private static $_objects;
    private static $_output;
    private static $_depth;

    public static function dumpAsString($var, $depth = 10, $highlight = false)
    {
        self::$_output = '';
        self::$_objects = [];
        self::$_depth = $depth;
        self::dumpInternal($var, 0);
        if ($highlight) {
            $result = highlight_string("<?php\n" . self::$_output, true);
            self::$_output = preg_replace('/&lt;\\?php<br \\/>/', '', $result, 1);
        }

        return self::$_output;
    }

    /**
     * @param mixed $var variable to be dumped
     * @param int $level depth level
     * @param bool $passwordKey whether password related key was present in previous iteration
     */
    private static function dumpInternal($var, $level, $passwordKey = false)
    {
        switch (gettype($var)) {
            case 'boolean':
                self::$_output .= $var ? 'true' : 'false';
                break;
            case 'integer':
                self::$_output .= "$var";
                break;
            case 'double':
                self::$_output .= "$var";
                break;
            case 'string':
                if ($passwordKey) {
                    self::$_output .= "'** PASSWORD HIDDEN **'";
                } else {
                    self::$_output .= "'" . addslashes($var) . "'";
                }
                break;
            case 'resource':
                self::$_output .= '{resource}';
                break;
            case 'NULL':
                self::$_output .= 'null';
                break;
            case 'unknown type':
                self::$_output .= '{unknown}';
                break;
            case 'array':
                if (self::$_depth <= $level) {
                    self::$_output .= '[...]';
                } elseif (empty($var)) {
                    self::$_output .= '[]';
                } else {
                    $keys = array_keys($var);
                    $spaces = str_repeat(' ', $level * 4);
                    self::$_output .= '[';
                    foreach ($keys as $key) {
                        self::$_output .= "\n" . $spaces . '    ';
                        self::dumpInternal($key, 0);
                        self::$_output .= ' => ';
                        self::dumpInternal($var[$key], $level + 1, strpos(strtolower($key), 'password') !== false);
                    }
                    self::$_output .= "\n" . $spaces . ']';
                }
                break;
            case 'object':
                if (($id = array_search($var, self::$_objects, true)) !== false) {
                    self::$_output .= get_class($var) . '#' . ($id + 1) . '(...)';
                } elseif (self::$_depth <= $level) {
                    self::$_output .= get_class($var) . '(...)';
                } else {
                    $id = array_push(self::$_objects, $var);
                    $className = get_class($var);
                    $spaces = str_repeat(' ', $level * 4);
                    self::$_output .= "$className#$id\n" . $spaces . '(';
                    if ('__PHP_Incomplete_Class' !== get_class($var) && method_exists($var, '__debugInfo')) {
                        $dumpValues = $var->__debugInfo();
                        if (!is_array($dumpValues)) {
                            throw new InvalidValueException('__debuginfo() must return an array');
                        }
                    } else {
                        $dumpValues = (array) $var;
                    }
                    foreach ($dumpValues as $key => $value) {
                        $keyDisplay = strtr(trim($key), "\0", ':');
                        self::$_output .= "\n" . $spaces . "    [$keyDisplay] => ";
                        self::dumpInternal($value, $level + 1);
                    }
                    self::$_output .= "\n" . $spaces . ')';
                }
                break;
        }
    }
}

And use your\namespace\here\FileTarget in configuration for logs. You can use all properties available for the selected target and now all fields containing password in their name with field value being string will log ** PASSWORD HIDDEN ** instead of actual password.

Sign up to request clarification or add additional context in comments.

1 Comment

Thank you so much for this! The logging of password fields should be built out of Yii as standard, this problem has been highlighted by the recent GitHub password logging.
0

The default target component only filters the keys one level deep.

So you can do !_POST.LoginForm to filter all fields of the LoginForm but not filter specific fields.

'logVars' = [
    '_GET',
    '_POST',
    '_FILES',
    '_COOKIE',
    '_SESSION',
    '_SERVER',
    '!_POST.LoginForm'
    ],

Will give you all the default logging except for any fields posted in LoginForm.

2 Comments

Any field for LoginForm is not suitable solution. I expect to see LoginForm.email and other fields. Actually LoginForm is just for instance.
Yes, my point was that you couldn't configure the default component to do the filtering as it only looks one level deep, the solution is to create your own class as Bizley has shown.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.