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

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

-~{}~ 28.09.10 13:22:

Но общая картина понятна, спасибо.
 
Автор оригинала: Вурдалак
korchasa, т.е. в том виде, что у тебя сделан сейчас ACL так сделать нельзя? :)
В том виде в котором у меня этого не надо, т.к. есть наследование ролей :Р
 
Еще я столкнулся с одним тонким моментом - когда у юзера несколько ролей, то сначала приоритет на проверку запрещение действия и уже потом на разрешение.
Мое 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
}
?
 
Вурдалак
Ну там же есть пример. 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:

Ну, если привелегии не важны ;)
 
Вурдалак
Так в том то и гибкость, что хранить я могу что и как угодно ;) ACL дает лишь инструмент для проверки и runtime-хранилище все добра. А привилегии мне таки важны.
 
korchasa, ну, вопрос скорее по теории, нежели по твоему классу (кстати, чем он от Zend_Acl отличается?). Имена ролей в коде у тебя фигурируют?
 
Автор оригинала: Вурдалак
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') ) {
    // ...
}
 
Вурдалак
А у меня их там и нет. Есть же интерфейсы для носителей роли и ресурса. По ним определяется динамически:
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» быть в коде не должна по-хорошему. Ну нет в таблице такой строки, допустим. Или она означает какую-то другую роль.

Просто набор базовых ролей, от которых всё остальное бы наследовалось, я позволить себе не могу...
 
Вурдалак
Давай ты объяснишь, какого ты хочешь добиться результата. Тогда я смогу сказать, как бы я это делал. Только не теоретические возможности, а конкретную задачу.
 
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
        }
    }
}
 
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: исправил опечатки.
 
Сверху