Права пользователей/модераторов

Вурдалак

Продвинутый новичок
korchasa, т.е. в том виде, что у тебя сделан сейчас ACL так сделать нельзя? :)

-~{}~ 28.09.10 13:22:

Но общая картина понятна, спасибо.
 

korchasa

LIMB infected
Автор оригинала: Вурдалак
korchasa, т.е. в том виде, что у тебя сделан сейчас ACL так сделать нельзя? :)
В том виде в котором у меня этого не надо, т.к. есть наследование ролей :Р
 

rotoZOOM

ACM maniac
Еще я столкнулся с одним тонким моментом - когда у юзера несколько ролей, то сначала приоритет на проверку запрещение действия и уже потом на разрешение.
Мое ACL (role_id, resource_id, action_id, access)
access := true | false
Если запись для конкретной триады (роль, ресурс, действие) не найден, то идем вверх по дереву ресурсов (ну это стандартно).
Так вот. Есть три роли:
- менеджер;
- специалист по фикусам;
- специалист по кактусам;

Менеджер имеет access = write на ресурс "управление", который включает в себя ресурсы:
- управление юзерами;
- управление почтой;
- управление категориями фикусов;
- управление категориями кактусов;
- и т.д.

специалист по фикусам имеет access = false на "управление категориями кактусов",
специалист по кактусам имеет access = false на "управление категориями фикусов".

Таким образом юзер имеющий роли "менеджер" и "специалист по фикусам" может все, что доступно менеджеру, кроме как доступ на write к ресурсу "управление категориями кактусов".

В итоге получается весьма гибкая система прав, собственно, почти вариант korchasa.
Возможно, не было смысла городить огород c запрещающим аксессом, жизнь покажет.
 

Вурдалак

Продвинутый новичок
korchasa, OK, т.е. ты предлагаешь нечто вроде этого:

PHP:
// Tables
// roles, resources, acl, user_roles

class User implements lmbRoleProviderInterface {
    // ...

    protected function onLoad()
    {
        Acl::instance()->addRole('?', $this->getRoles());
    }

    public function getRole()
    {
        return '?';
    }
}

// ...

if( $acl->isAllowed($user, 'ban') ) {
    // User has the banhammer
}
?
 

korchasa

LIMB infected
Вурдалак
Ну там же есть пример. User->getRole() возвращает базовую роль guest, user или admin. User отнаследован от guest, admin от user. Поэтому мне не нужно поддержку сразу нескольких ролей одним носителем. Если мне нужно будет ввести админов по категориям, например, то я сделаю это через резолвинг в самой категории:
PHP:
class Category implements lmbRoleResolver {
  function getRoleFor(User $user)
  {
    if($this->getAdministratorId() === $user->getId())
      return 'category_administrator';
  }
}
 

Вурдалак

Продвинутый новичок
korchasa, я же не спрашиваю что возвращает User->getRole(). Я спросил то ли ты имеешь в виду. Адекватен ли код, то есть.

Мне не нравится привязка к имени роли в последнем примере. Список ролей хранится в таблице, что само по себе подразумевает возможность удаления таковых. Не лучше ли было бы тогда уж тупо ввести метод а-ля:
PHP:
class Category implements accessibleInterface {

    public checkAccessFor(User $user)
    {
        return in_array($user->id, $this->getModeratorList());
    }
}
?

-~{}~ 29.09.10 01:08:

Ну, если привелегии не важны ;)
 

korchasa

LIMB infected
Вурдалак
Так в том то и гибкость, что хранить я могу что и как угодно ;) ACL дает лишь инструмент для проверки и runtime-хранилище все добра. А привилегии мне таки важны.
 

Вурдалак

Продвинутый новичок
korchasa, ну, вопрос скорее по теории, нежели по твоему классу (кстати, чем он от Zend_Acl отличается?). Имена ролей в коде у тебя фигурируют?
 

korchasa

LIMB infected
Автор оригинала: Вурдалак
korchasa, ну, вопрос скорее по теории, нежели по твоему классу (кстати, чем он от Zend_Acl отличается?). Имена ролей в коде у тебя фигурируют?
Да ничем почти. Стандартная acl с наследованием ролей. Там сильно по другому и не напишешь. Ну только объектов для ресурсов и ролей у меня нет, и динамический резолвинг. Хотя они его тоже недавно вроде бы написали.
 

Вурдалак

Продвинутый новичок
Вроде так можно обойтись без имён ролей в коде:
PHP:
class Category implements accessibleInterface {
    public checkPrivilegeFor(User $user, $privilege)
    {
        switch( $privilege ) {
            case 'view':
                return TRUE;

            case 'moderate':
                return in_array($user->id, $this->getModeratorList());

            default:
                return FALSE;
        }
    }
}

// ...

if( $acl->isAllowed($user, 'category', 'view') ) {
    // ...
}
 

korchasa

LIMB infected
Вурдалак
А у меня их там и нет. Есть же интерфейсы для носителей роли и ресурса. По ним определяется динамически:
PHP:
  $acl->isAllowed($user, $article, 'view')
+ есть macro-тэг
Код:
{{allowed resource="<name|provider>" [role="<name|provider>"] [privilege="<name>"]}}foo{{/allowed}}
 

Вурдалак

Продвинутый новичок
korchasa, мы как-то друг друга не понимаем.

Вот ты сам пример выше приводил:
PHP:
class Category implements lmbRoleResolver {
  function getRoleFor(User $user)
  {
    if($this->getAdministratorId() === $user->getId())
      return 'category_administrator';
  }
}
Строка «category_administrator» быть в коде не должна по-хорошему. Ну нет в таблице такой строки, допустим. Или она означает какую-то другую роль.

Просто набор базовых ролей, от которых всё остальное бы наследовалось, я позволить себе не могу...
 

korchasa

LIMB infected
Вурдалак
Давай ты объяснишь, какого ты хочешь добиться результата. Тогда я смогу сказать, как бы я это делал. Только не теоретические возможности, а конкретную задачу.
 

Вурдалак

Продвинутый новичок
PHP:
class Controller implements lmbResourceProviderInterface
{
    // ...

    public before()
    {
        // ...

        $acl = Acl::instance();

        if( ! $acl->isAllowed($this->getUser(), $this, $this->getRequest()->getAction()) )
        {
            // Redirect to http://example.com/noaccess.html
        }
    }

    public function getResource()
    {
        return $this->getRequest()->getController();
    }
}

class Controller_Article extends Controller
{
    public function actionView($id = NULL)
    {
        // ...
    }

    public function actionEdit($id = NULL)
    {
        // ...
    }
}
Код понятен? Как тут реализовать доступ к редактированию статьи её владельцу?

Через lmbRoleResolver как-то не очень красиво.

-~{}~ 29.09.10 21:27:

Хотя, IMHO, тут лучше просто проверку в actionEdit() сделать надо.
 

Вурдалак

Продвинутый новичок
Автор оригинала: Вурдалак
Мне не нравится привязка к имени роли в последнем примере. Список ролей хранится в таблице, что само по себе подразумевает возможность удаления таковых.
В общем, я вижу решение в использовании обычных проверок в самом методе в таких скользких случаях. Что-то вроде
PHP:
class Controller_Article extends Controller
{
    public function actionView($id = NULL)
    {
        // ...
    }

    public function actionEdit($id = NULL)
    {
        $article = Model::factory('Article')->find($id);

        $isModerator = Acl::instance()->isAllowed($this->getUser(), $this, 'edit') AND $article->inModeratorList($this->getUser());
        $isOwner = $article->getUser()->getId() == $article->getOwnerId();

        if( $isModerator OR $isOwner )
        {
            // User can edit the article
        }
    }
}
 

korchasa

LIMB infected
PHP:
$acl->addRole('user');
$acl->addRole('owner');
$acl->addRole('moderator', 'owner');
$acl->addResource('article');
$acl->allow('owner', 'article', 'edit');
...

class User implements RoleProvider
{
  function getRole()
  {
    return 'user';
  }
}

class Article implements ResourceProvider, RoleResolver
{
  function getRoleFor($user)
  {
    if ($this->isModeratorList($user))
      return 'moderator';
    if ($this->owner_id = $user->id)
      return 'owner';      
  }

  function getResource()
  {
    return 'article';
  }
}

class Controller_Article extends Controller
{
    public function actionEdit($id = NULL) 
    { 
        $article = Model::factory('Article')->find($id); 
        if( Acl::instance()->isAllowed($this->getUser(), $article, 'edit') ) 
        { 
            // User can edit the article 
        } 
    } 
}
Как то так.
+ лаконичность
+ правила лежат в одном месте
- проверка может быть сложной, а по вызову это не понятно (абстракция, ага)
- в getRoleFor() необходимо контроллировать порядок проверок ( от ролей с большим количеством прав, к меньшим )
 

Вурдалак

Продвинутый новичок
Примерно так выглядит решение, которое мне кажется наиболее правильным:
PHP:
class Acl
{
    public static function instance()
    {
        if( self::$_instance === NULL ) {
            // ...
    
            foreach($db->getRoles() as $role) {
                $acl->addRole('db:' . $role['name']);
            }

            foreach($db->getResources() as $resource) {
                // Names like "controller:article", "model:article", ...
                $acl->addResource(new Zend_Acl_Resource($resource['namespace'] . ':' . $resource['name']));
            }

            foreach($db->getRules() as $rule) {
                // ...
            }
    
            // ...
        }
    
        return self::$_instance;
    }
}

class User implements RoleProvider
{
    protected $_role;

    // ...

    public function getRole()
    {
        if( $this->_role === NULL ) {
            $this->_role = 'user:' . $this->id;
            Acl::instance()->addRole($this->_role, $this->getRoles());
        }

        return $this->_role;
    }
}

class Article implements ResourceProvider, RoleResolver
{
    protected $_resourceId = 'model:article';

    public function onLoad()
    {
        Acl::instance()
        ->addRole($_resourceId . ':owner')
        ->addRole($_resourceId . ':moderator', $_resourceId . ':owner')
        ->allow($_resourceId . ':owner', $_resourceId, 'edit');
    }

    public function getRoleFor($role)
    {            
        if( $this->inModeratorList($user) )
            return $_resourceId . ':moderator';

        if( $this->owner_id = $user->id )
            return $_resourceId . ':owner';
    }

    public function getResource()
    {
        return $_resourceId;
    }
}
Но использовать его на практике — большая проблема. :)

UPD: исправил опечатки.
 
Сверху