Подтвердить что ты не робот

Настройка базы данных ACL CakePHP: структура ARO/ACO?

Я пытаюсь реализовать ACL в CakePHP. После прочтения документации в руководстве cake manual, а также нескольких других уроков, сообщений в блогах и т.д., Я нашел отличный учебник Aran Johnson, который помог заполнить многие пробелов. Его примеры, похоже, противоречат другим, которые я видел, хотя в нескольких местах - особенно в структуре дерева ARO, которую он использует.

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

Как вы настраиваете свои ARO и ACO в CakePHP? Все советы приветствуются!

4b9b3361

Ответ 1

Встроенная система ACL CakePHP действительно мощная, но плохо документированная с точки зрения фактических деталей реализации. Система, которую мы использовали с некоторыми успехами в ряде проектов на основе CakePHP, выглядит следующим образом.

Это модификация некоторых систем доступа на уровне группы, которые были задокументированы в другом месте. Наша система нацелена на то, чтобы иметь простую систему, в которой пользователи авторизованы на уровне группы, но у них могут быть определенные дополнительные права на элементы, созданные ими или на основе каждого пользователя. Мы хотели избежать необходимости создавать конкретную запись для каждого пользователя (или, более конкретно, для каждой ARO) в таблице aros_acos.

У нас есть таблица "Пользователи" и таблица "Роли".

Пользователи

user_id, user_name, role_id

Роли

id, role_name

Создайте дерево ARO для каждой роли (обычно у нас есть 4 роли - Unauthorized Guest (id 1), Авторизованный пользователь (id 2), Модератор сайта (id 3) и Администратор (id 4)):

cake acl create aro / Role.1

cake acl create aro 1 Role.2 ... etc ...

После этого вам нужно использовать SQL или phpMyAdmin или аналогичные для добавления псевдонимов для всех этих, так как инструмент командной строки cake не делает этого. Мы используем "Role- {id}" и "User-{id}" для всех наших.

Затем мы создаем ROOT ACO -

cake acl create aco / 'ROOT'

а затем создайте ACOs для всех контроллеров в этом ROOT:

cake acl create aco 'ROOT' 'MyController' ... etc ...

До сих пор так нормально. Мы добавляем дополнительное поле в таблицу aros_acos с именем _editown, которую мы можем использовать в качестве дополнительного действия в компоненте action ACL компонента.

CREATE TABLE IF NOT EXISTS `aros_acos` (
`id` int(11) NOT NULL auto_increment,
`aro_id` int(11) default NULL,
`aco_id` int(11) default NULL,
`_create` int(11) NOT NULL default '0',
`_read` int(11) NOT NULL default '0',
`_update` int(11) NOT NULL default '0',
`_delete` int(11) NOT NULL default '0',
`_editown` int(11) NOT NULL default '0',
PRIMARY KEY  (`id`),
KEY `acl` (`aro_id`,`aco_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8;

Затем мы можем настроить компонент Auth для использования метода "crud", который проверяет запрашиваемый контроллер/действие на AclComponent:: check(). В app_controller мы имеем что-то вроде:

private function setupAuth() {
    if(isset($this->Auth)) {
        ....
        $this->Auth->authorize = 'crud';
        $this->Auth->actionMap = array( 'index'     => 'read',
                        'add'       => 'create',
                        'edit'      => 'update'
                        'editMine'  => 'editown',
                        'view'      => 'read'
                        ... etc ...
                        );
        ... etc ...
    }
}

Опять же, это довольно стандартный материал CakePHP. Затем у нас есть метод checkAccess в AppController, который добавляет в материал группового уровня, чтобы проверить, следует ли проверять группу ARO или пользовательскую ARO для доступа:

private function checkAccess() {
    if(!$user = $this->Auth->user()) {
        $role_alias = 'Role-1';
        $user_alias = null;
    } else {
        $role_alias = 'Role-' . $user['User']['role_id'];
        $user_alias = 'User-' . $user['User']['id'];
    }

    // do we have an aro for this user?
    if($user_alias && ($user_aro = $this->User->Aro->findByAlias($user_alias))) {
        $aro_alias = $user_alias;
    } else {
        $aro_alias = $role_alias;
    }

    if ('editown' == $this->Auth->actionMap[$this->action]) {
        if($this->Acl->check($aro_alias, $this->name, 'editown') and $this->isMine()) {
            $this->Auth->allow();
        } else {
            $this->Auth->authorize = 'controller';
            $this->Auth->deny('*');
        }
    } else {
        // check this user-level aro for access
        if($this->Acl->check($aro_alias, $this->name, $this->Auth->actionMap[$this->action])) {
            $this->Auth->allow();
        } else {
            $this->Auth->authorize = 'controller';
            $this->Auth->deny('*');
        }
    }
}

Методы setupAuth() и checkAccess() вызываются в обратном вызове AppController beforeFilter(). Там также существует метод isMine в AppControler (см. Ниже), который просто проверяет, что user_id запрашиваемого элемента совпадает с текущим аутентифицированным пользователем. Я оставил это для ясности.

Это действительно все, что нужно. Затем вы можете разрешить/запретить отдельным группам доступ к определенным acos -

cake acl grant 'Role-2' 'MyController' 'read'

cake acl grant 'Role-2' 'MyController' 'editown'

cake acl deny 'Role-2' 'MyController' 'update'

cake acl deny 'Role-2' 'MyController' 'delete'

Я уверен, что вы получите картину.

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

- изменить -

В соответствии с запросом здесь отредактировано (чисто для наглядности - там много материала в нашем шаблоне кода, который здесь бессмыслен) isMine(), который мы имеем в нашем AppController. Я также удалил много ошибок, но в этом суть:

function isMine($model=null, $id=null, $usermodel='User', $foreignkey='user_id') {
    if(empty($model)) {
        // default model is first item in $this->uses array
        $model = $this->uses[0];
    }

    if(empty($id)) {
        if(!empty($this->passedArgs['id'])) {
        $id = $this->passedArgs['id'];
        } elseif(!empty($this->passedArgs[0])) {
            $id = $this->passedArgs[0];
        }
    }

    if(is_array($id)) {
        foreach($id as $i) {
            if(!$this->_isMine($model, $i, $usermodel, $foreignkey)) {
                return false;
            }
        }

        return true;
    }

    return $this->_isMine($model, $id, $usermodel, $foreignkey);
}


function _isMine($model, $id, $usermodel='User', $foreignkey='user_id') {
    $user = Configure::read('curr.loggedinuser'); // this is set in the UsersController on successful login

    if(isset($this->$model)) {
        $model = $this->$model;
    } else {
        $model = ClassRegistry::init($model);
    }

    //read model
    if(!($record = $model->read(null, $id))) {
        return false;
    }

    //get foreign key
    if($usermodel == $model->alias) {
        if($record[$model->alias][$model->primaryKey] == $user['User']['id']) {
            return true;
        }
    } elseif($record[$model->alias][$foreignkey] == $user['User']['id']) {
        return true;
    }

    return false;
}