yiisoft / yii2

Yii 2: The Fast, Secure and Professional PHP Framework
http://www.yiiframework.com
BSD 3-Clause "New" or "Revised" License
14.23k stars 6.91k forks source link

yii\rbac\Rule serialization issue #10176

Closed akireikin closed 8 years ago

akireikin commented 8 years ago

Consider the following example.

class FooRule extends yii\rbac\Rule
{
    public $name = 'foo';
    private $dependentClassName;

    public function __construct($dependentClassName, $config = [])
    {
        $this->dependedClassName = $dependedClassName;
        parent::__construct($config);
    }

    public function execute($user, $item, $params)
    {
         $dependentClass = Yii::$container->get($this->dependentClassName);
         // logic
    }
}

Using this class with yii\rbac\DbManager causes unserialize(): Error. I made a little bit of research and it seems the error caused by the private field unserialization.

From http://php.net/en/serialize.

Object's private members have the class name prepended to the member name; protected members have a '*' prepended to the member name. These prepended values have null bytes on either side.

...

Note that this is a binary string which may include null bytes, and needs to be stored and handled as such. For example, serialize() output should generally be stored in a BLOB field in a database, rather than a CHAR or TEXT field.

Of course, I can use public field instead, but it looks like treating the symptom, not the cause. Anyway, at least corresponding docs should be improved.

cebe commented 8 years ago

Using this class with yii\rbac\DbManager causes unserialize(): Error

can you be more specific about how you use the class and also provide the stacktrace of the error?

akireikin commented 8 years ago

Config

return [
    // ...
    'components' => [
        'authManager' => [
            'class' => 'yii\rbac\DbManager',
        ],
        // ...
    ],
];

AuthorRule

class AuthorRule extends yii\rbac\Rule
{
    private $activeRecordClass;

    public function __construct($activeRecordClass, $config = [])
    {
        $this->activeRecordClass = $activeRecordClass;
        parent::__construct($config);
    }

    public function execute($user, $item, $params)
    {
         $class = $this->activeRecordClass;
         $ar = $class::findOne($params['id']);

         if (!$ar) {
             throw new yii\web\NotFoundHttpException();
         }

         return $user === $ar->user_id;
    }
}

AccessFilter

class AccessFilter extends \yii\base\ActionFilter
{
    public function beforeAction($action)
    {
        if (Yii::app()->user->can($action->getUniqueId(), ['id' => Yii::app()->request->get('id'))) {
            return true;
        }
        throw new yii\web\ForbiddenHttpException();
    }
}

Controller

class PostController
{
    public $allowUnauthorized = ['index'];

    public function behaviors()
    {
        return [
            // 401
            'auth'   => [
                'class'      => QueryParamAuth::class,
                'tokenParam' => 'token',
                'except'     => $this->allowUnauthorized,
            ],
            // 403
            'access' => [
                'class'  => AccessFilter::class,
                'except' => $this->allowUnauthorized,
            ],
        ];
    }

    /* 
    |---------
    | Actions 
    |---------
    |
    */
}

Command for building rbac tree

$auth = Yii::$app->authManager;

// add the rule
$rule = new AuthorRule(Post::class);
$auth->add($rule);

// permissions
$createPost = $auth->createPermission('post/create');
$auth->add($createPost);

$updatePost = $auth->createPermission('post/update');
$auth->add($updatePost);

$updateOwnPost = $auth->createPermission('post/update:own');
$updateOwnPost->ruleName = $rule->name;
$auth->add($updateOwnPost);
$auth->addChild($updateOwnPost, $updatePost);

// roles
$author = $auth->createRole('author');
$auth->add($author);
$auth->addChild($author, $createPost);
$auth->addChild($author, $updateOwnPost);

$admin = $auth->createRole('admin');
$auth->add($admin);
$auth->addChild($admin, $updatePost);
$auth->addChild($admin, $author);

// assigning roles to users
$auth->assign($author, 2);
$auth->assign($admin, 1);

RBAC tree saved in DB without any error. The following error occurs when access control is executed.

{
    "name": "PHP Notice",
    "message": "unserialize(): Error at offset 77 of 81 bytes",
    "code": 8,
    "type": "yii\\base\\ErrorException",
    "file": "/var/www/myapp/api/vendor/yiisoft/yii2/rbac/DbManager.php",
    "line": 573,
    "stack-trace": [
        "#0 /var/www/myapp/api/vendor/yiisoft/yii2/rbac/BaseManager.php(206): yii\\rbac\\DbManager->getRule()",
        "#1 /var/www/myapp/api/vendor/yiisoft/yii2/rbac/DbManager.php(192): yii\\rbac\\BaseManager->executeRule()",
        "#2 /var/www/myapp/api/vendor/yiisoft/yii2/rbac/DbManager.php(206): yii\\rbac\\DbManager->checkAccessRecursive()",
        "#3 /var/www/myapp/api/vendor/yiisoft/yii2/rbac/DbManager.php(126): yii\\rbac\\DbManager->checkAccessRecursive()",
        "#4 /var/www/myapp/api/vendor/yiisoft/yii2/web/User.php(661): yii\\rbac\\DbManager->checkAccess()",
        "#5 /var/www/myapp/api/core/http/filters/AccessFilter.php(53): yii\\web\\User->can()",
        "#6 /var/www/myapp/api/vendor/yiisoft/yii2/base/ActionFilter.php(71): app\\core\\http\\filters\\AccessFilter->beforeAction()",
        "#7 /var/www/myapp/api/vendor/yiisoft/yii2/base/Component.php(541): yii\\base\\ActionFilter->beforeFilter()",
        "#8 /var/www/myapp/api/vendor/yiisoft/yii2/base/Component.php(541): ::call_user_func:{/var/www/myapp/api/vendor/yiisoft/yii2/base/Component.php:541}()",
        "#9 /var/www/myapp/api/vendor/yiisoft/yii2/base/Controller.php(263): yii\\base\\Component->trigger()",
        "#10 /var/www/myapp/api/vendor/yiisoft/yii2/web/Controller.php(108): yii\\base\\Controller->beforeAction()",
        "#11 /var/www/myapp/api/vendor/yiisoft/yii2/base/Controller.php(149): yii\\web\\Controller->beforeAction()",
        "#12 /var/www/myapp/api/vendor/yiisoft/yii2/base/Module.php(455): yii\\base\\Controller->runAction()",
        "#13 /var/www/myapp/api/vendor/yiisoft/yii2/web/Application.php(84): yii\\base\\Module->runAction()",
        "#14 /var/www/myapp/api/vendor/yiisoft/yii2/base/Application.php(375): yii\\web\\Application->handleRequest()",
        "#15 /var/www/myapp/frontend/app/api/index.php(25): yii\\base\\Application->run()",
        "#16 {main}"
    ]
}
samdark commented 8 years ago

Can you get what's stored in DB and post here?

akireikin commented 8 years ago

Table auth_rule

namedatacreated_atupdated_at
authorO:40:"app\modules\permissions\rules\AuthorRule":4:{s:4:"name";s:6:"author";s:53:"14490513441449051344
samdark commented 8 years ago

Aha. Seems you have it trimmed. What's type of the data column?

akireikin commented 8 years ago

The type is text. BTW, I use PostgreSQL.

samdark commented 8 years ago

Then it's not the case and the reason for trimming is elsewhere. PostgreSQL text could hold about 4GB w/o problems.

SilverFire commented 8 years ago

@akireikin did you solve your problem?

akireikin commented 8 years ago

@SilverFire I stopped using private and protected fields in Rule classes.

SilverFire commented 8 years ago

@akireikin Did it fix serialization trimming in DB?

SilverFire commented 8 years ago

I'm closing the issue because @akireikin is inactive and the problem is not verified.

SilverFire commented 8 years ago

Found, that it's a PHP peculiarity https://github.com/yiisoft/yii2/issues/10823#issuecomment-183631269