Настройка базы данных ACL CakePHP: структура ARO / ACO? - PullRequest
21 голосов
/ 10 сентября 2008

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

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

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

1 Ответ

49 голосов
/ 11 сентября 2008

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

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

У нас есть таблица Users и таблица Roles.

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

user_id, user_name, role_id

Роли

id, role_name

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

cake acl create aro / Role.1

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

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

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

cake acl create aco / 'ROOT'

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

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

Пока все нормально. Мы добавляем дополнительное поле в таблицу aros_acos с именем _editown, которое мы можем использовать в качестве дополнительного действия в actionMap компонента 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(). В AppControler также есть метод isMine (см. Ниже), который просто проверяет, совпадает ли 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;
}
...