Migrate permissions system to use hard-coded action lists, and update the Permissions management page to solely allow role management.

This commit is contained in:
Buster Silver 2016-10-07 15:57:42 -05:00
parent 9a927d97f1
commit cf69f61cf7
12 changed files with 232 additions and 252 deletions

View File

@ -5,9 +5,9 @@
namespace App;
use Entity\RolePermission;
use Entity\User;
use Entity\Role;
use Entity\RoleHasAction;
class Acl
{
@ -32,7 +32,7 @@ class Acl
protected function init()
{
if (null === $this->_actions)
$this->_actions = $this->_em->getRepository(RoleHasAction::class)->getActionsForAllRoles();
$this->_actions = $this->_em->getRepository(RolePermission::class)->getActionsForAllRoles();
}
/**

View File

@ -1,40 +0,0 @@
<?php
namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="action")
* @Entity(repositoryClass="ActionRepository")
*/
class Action extends \App\Doctrine\Entity
{
public function __construct()
{
$this->is_global = false;
$this->has_role = new ArrayCollection;
}
/**
* @Column(name="id", type="integer")
* @Id
* @GeneratedValue(strategy="IDENTITY")
*/
protected $id;
/** @Column(name="name", type="string", length=100, nullable=true) */
protected $name;
/** @Column(name="is_global", type="boolean") */
protected $is_global;
/** @OneToMany(targetEntity="Entity\RoleHasAction", mappedBy="action") */
protected $has_role;
}
use App\Doctrine\Repository;
class ActionRepository extends Repository
{
}

View File

@ -12,7 +12,7 @@ class Role extends \App\Doctrine\Entity
public function __construct()
{
$this->users = new ArrayCollection;
$this->has_action = new ArrayCollection;
$this->permissions = new ArrayCollection;
}
/**
@ -28,6 +28,6 @@ class Role extends \App\Doctrine\Entity
/** @ManyToMany(targetEntity="User", mappedBy="roles")*/
protected $users;
/** @OneToMany(targetEntity="Entity\RoleHasAction", mappedBy="role") */
protected $has_action;
/** @OneToMany(targetEntity="Entity\RolePermission", mappedBy="role") */
protected $permissions;
}

View File

@ -4,12 +4,12 @@ namespace Entity;
use Doctrine\Common\Collections\ArrayCollection;
/**
* @Table(name="role_has_actions", uniqueConstraints={
* @UniqueConstraint(name="role_action_unique_idx", columns={"role_id","action_id","station_id"})
* @Table(name="role_permissions", uniqueConstraints={
* @UniqueConstraint(name="role_permission_unique_idx", columns={"role_id","action_name","station_id"})
* })
* @Entity(repositoryClass="RoleHasActionRepository")
* @Entity(repositoryClass="RolePermissionRepository")
*/
class RoleHasAction extends \App\Doctrine\Entity
class RolePermission extends \App\Doctrine\Entity
{
/**
* @Column(name="id", type="integer")
@ -22,23 +22,15 @@ class RoleHasAction extends \App\Doctrine\Entity
protected $role_id;
/**
* @ManyToOne(targetEntity="Role", inversedBy="has_actions")
* @ManyToOne(targetEntity="Role", inversedBy="permissions")
* @JoinColumns({
* @JoinColumn(name="role_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
protected $role;
/** @Column(name="action_id", type="integer") */
protected $action_id;
/**
* @ManyToOne(targetEntity="Action", inversedBy="has_roles")
* @JoinColumns({
* @JoinColumn(name="action_id", referencedColumnName="id", onDelete="CASCADE")
* })
*/
protected $action;
/** @Column(name="action_name", type="string", length=50, nullable=false) */
protected $action_name;
/** @Column(name="station_id", type="integer", nullable=true) */
protected $station_id;
@ -54,54 +46,24 @@ class RoleHasAction extends \App\Doctrine\Entity
use App\Doctrine\Repository;
class RoleHasActionRepository extends Repository
class RolePermissionRepository extends Repository
{
public function getActionsForAllRoles()
{
$role_has_actions = $this->_em->createQuery('SELECT rha, a FROM '.$this->_entityName.' rha JOIN rha.action a')
->getArrayResult();
$all_permissions = $this->fetchArray();
$roles = [];
foreach($role_has_actions as $row)
foreach($all_permissions as $row)
{
if ($row['station_id'])
$roles[$row['role_id']]['stations'][$row['station_id']][] = $row['action']['name'];
$roles[$row['role_id']]['stations'][$row['station_id']][] = $row['action_name'];
else
$roles[$row['role_id']]['global'][] = $row['action']['name'];
$roles[$row['role_id']]['global'][] = $row['action_name'];
}
return $roles;
}
public function getSelectableActions()
{
$actions = [];
$all_actions = $this->_em->getRepository(Action::class)->fetchArray();
$all_stations = $this->_em->getRepository(Station::class)->fetchArray();
foreach($all_actions as $action)
{
if ($action['is_global'])
{
$actions['global'][$action['id']] = $action['name'];
}
else
{
foreach($all_stations as $station)
{
if (!isset($actions['stations'][$station['id']]))
$actions['stations'][$station['id']] = ['name' => $station['name'], 'actions' => []];
$actions['stations'][$station['id']]['actions'][$action['id']] = $action['name'];
}
}
}
return $actions;
}
public function getActionsForRole(Role $role)
{
$role_has_action = $this->findBy(['role_id' => $role->id]);
@ -110,9 +72,9 @@ class RoleHasActionRepository extends Repository
foreach($role_has_action as $row)
{
if ($row['station_id'])
$result['actions_'.$row['station_id']][] = $row['action_id'];
$result['actions_'.$row['station_id']][] = $row['action_name'];
else
$result['actions_global'][] = $row['action_id'];
$result['actions_global'][] = $row['action_name'];
}
return $result;
@ -120,7 +82,7 @@ class RoleHasActionRepository extends Repository
public function setActionsForRole(Role $role, $post_values)
{
$this->_em->createQuery('DELETE FROM '.$this->_entityName.' rha WHERE rha.role_id = :role_id')
$this->_em->createQuery('DELETE FROM '.$this->_entityName.' rp WHERE rp.role_id = :role_id')
->setParameter('role_id', $role->id)
->execute();
@ -131,17 +93,17 @@ class RoleHasActionRepository extends Repository
if ($post_key_action !== 'actions' || empty($post_value))
continue;
foreach((array)$post_value as $action_id)
foreach((array)$post_value as $action_name)
{
$record_info = [
'role_id' => $role->id,
'action_id' => $action_id
'action_name' => $action_name,
];
if ($post_key_id !== 'global')
$record_info['station_id'] = $post_key_id;
$record = new RoleHasAction;
$record = new RolePermission;
$record->fromArray($this->_em, $record_info);
$this->_em->persist($record);

View File

@ -0,0 +1,47 @@
<?php
namespace Migration;
use Doctrine\DBAL\Migrations\AbstractMigration;
use Doctrine\DBAL\Schema\Schema;
/**
* Auto-generated Migration: Please modify to your needs!
*/
class Version20161007195027 extends AbstractMigration
{
/**
* @param Schema $schema
*/
public function up(Schema $schema)
{
// this up() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE role_permissions (id INT AUTO_INCREMENT NOT NULL, role_id INT NOT NULL, station_id INT DEFAULT NULL, action_name VARCHAR(50) NOT NULL, INDEX IDX_1FBA94E6D60322AC (role_id), INDEX IDX_1FBA94E621BDB235 (station_id), UNIQUE INDEX role_permission_unique_idx (role_id, action_name, station_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE role_permissions ADD CONSTRAINT FK_1FBA94E6D60322AC FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE role_permissions ADD CONSTRAINT FK_1FBA94E621BDB235 FOREIGN KEY (station_id) REFERENCES station (id) ON DELETE CASCADE');
$this->addSql('INSERT INTO role_permissions (role_id, action_name, station_id) SELECT rha.role_id, a.name, rha.station_id FROM role_has_actions AS rha INNER JOIN action AS a ON rha.action_id = a.id');
$this->addSql('ALTER TABLE role_has_actions DROP FOREIGN KEY FK_50EEC1BD9D32F035');
$this->addSql('DROP TABLE action');
$this->addSql('DROP TABLE role_has_actions');
}
/**
* @param Schema $schema
*/
public function down(Schema $schema)
{
// this down() migration is auto-generated, please modify it to your needs
$this->abortIf($this->connection->getDatabasePlatform()->getName() != 'mysql', 'Migration can only be executed safely on \'mysql\'.');
$this->addSql('CREATE TABLE action (id INT AUTO_INCREMENT NOT NULL, name VARCHAR(100) DEFAULT NULL COLLATE utf8_unicode_ci, is_global TINYINT(1) NOT NULL, PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('CREATE TABLE role_has_actions (id INT AUTO_INCREMENT NOT NULL, station_id INT DEFAULT NULL, action_id INT NOT NULL, role_id INT NOT NULL, UNIQUE INDEX role_action_unique_idx (role_id, action_id, station_id), INDEX IDX_50EEC1BDD60322AC (role_id), INDEX IDX_50EEC1BD21BDB235 (station_id), INDEX IDX_50EEC1BD9D32F035 (action_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE = InnoDB');
$this->addSql('ALTER TABLE role_has_actions ADD CONSTRAINT FK_50EEC1BD21BDB235 FOREIGN KEY (station_id) REFERENCES station (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE role_has_actions ADD CONSTRAINT FK_50EEC1BD9D32F035 FOREIGN KEY (action_id) REFERENCES action (id) ON DELETE CASCADE');
$this->addSql('ALTER TABLE role_has_actions ADD CONSTRAINT FK_50EEC1BDD60322AC FOREIGN KEY (role_id) REFERENCES role (id) ON DELETE CASCADE');
$this->addSql('DROP TABLE role_permissions');
}
}

View File

@ -0,0 +1,30 @@
<?php
/**
* Module-specific actions (permissions)
*/
return [
'global' => [
'administer all',
'view administration',
'administer settings',
'administer api keys',
'administer user accounts',
'administer permissions',
'administer stations',
],
'station' => [
],
];
/*
['view station management', 0],
['view station reports', 0],
['manage station profile', 0],
['manage station broadcasting', 0],
['manage station streamers', 0],
['manage station media', 0],
['manage station automation', 0],
];
*/

View File

@ -27,7 +27,7 @@ return [
'icon' => 'fa fa-user',
'permission' => 'administer user accounts',
],
_('Roles and Permissions') => [
_('Permissions') => [
'url' => 'admin:permissions:index',
'icon' => 'fa fa-key',
'permission' => 'administer permissions',

View File

@ -3,10 +3,28 @@
* Edit Role Form
*/
/** @var array */
$module_config = $di['module_config'];
$actions = [];
foreach($module_config as $module_name => $config_row)
{
$module_actions = $config_row->actions->toArray();
if (!empty($module_actions))
{
foreach($module_actions['global'] as $action_name)
$actions['global'][$action_name] = $action_name;
foreach($module_actions['station'] as $action_name)
$actions['station'][$action_name] = $action_name;
}
}
/** @var \Doctrine\ORM\EntityManager $em */
$em = $di['em'];
$all_actions = $em->getRepository(\Entity\RoleHasAction::class)->getSelectableActions();
$all_stations = $em->getRepository(\Entity\Station::class)->fetchArray();
$form_config = [
'method' => 'post',
@ -23,30 +41,31 @@ $form_config = [
],
],
],
];
$form_config['groups']['grp_global'] = [
'legend' => _('System-Wide Permissions'),
'elements' => [
'grp_global' => [
'legend' => _('System-Wide Permissions'),
'elements' => [
'actions_global' => ['multiCheckbox', [
'label' => _('Actions'),
'multiOptions' => $all_actions['global'],
]],
'actions_global' => ['multiCheckbox', [
'label' => _('Actions'),
'multiOptions' => $actions['global'],
]],
],
],
],
];
foreach($all_actions['stations'] as $station_id => $station_info)
foreach($all_stations as $station)
{
$form_config['groups']['grp_station_'.$station_id] = [
'legend' => $station_info['name'],
$form_config['groups']['grp_station_'.$station['id']] = [
'legend' => $station['name'],
'elements' => [
'actions_'.$station_id => ['multiCheckbox', [
'actions_'.$station['id'] => ['multiCheckbox', [
'label' => _('Actions'),
'multiOptions' => $station_info['actions'],
'multiOptions' => $actions['station'],
]],
],

View File

@ -1,9 +1,8 @@
<?php
namespace Modules\Admin\Controllers;
use \Entity\Action;
use \Entity\Role;
use Entity\RoleHasAction;
use Entity\Role;
use Entity\RolePermission;
class PermissionsController extends BaseController
{
@ -14,50 +13,31 @@ class PermissionsController extends BaseController
public function indexAction()
{
$this->view->actions = $this->em->getRepository(Action::class)->fetchArray(false, 'name');
$this->view->roles = $this->em->getRepository(Role::class)->fetchArray(false, 'name');
}
public function editactionAction()
{
$form = new \App\Form($this->current_module_config->forms->action->toArray());
if ($this->hasParam('id'))
$all_roles = $this->em->createQuery('SELECT r, rp, s FROM Entity\Role r LEFT JOIN r.users u LEFT JOIN r.permissions rp LEFT JOIN rp.station s ORDER BY r.id ASC')
->getArrayResult();
$roles = [];
foreach($all_roles as $role)
{
$record = $this->em->getRepository(Action::class)->find($this->getParam('id'));
$form->setDefaults($record->toArray($this->em));
$role['permissions_global'] = [];
$role['permissions_station'] = [];
foreach($role['permissions'] as $permission)
{
if ($permission['station'])
$role['permissions_station'][$permission['station']['name']][] = $permission['action_name'];
else
$role['permissions_global'][] = $permission['action_name'];
}
$roles[] = $role;
}
if(!empty($_POST) && $form->isValid($_POST))
{
$data = $form->getValues();
if (!($record instanceof Action))
$record = new Action;
$record->fromArray($this->em, $data);
$this->em->persist($record);
$this->em->flush();
$this->alert(_('Record updated.'), 'green');
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
}
return $this->renderForm($form, 'edit', _('Edit Record'));
}
public function deleteactionAction()
{
$action = $this->em->getRepository(Action::class)->find($this->getParam('id'));
if ($action)
$action->delete();
$this->alert(_('Record deleted.'), 'green');
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
$this->view->roles = $roles;
}
public function rolemembersAction()
public function membersAction()
{
$roles = $this->em->createQuery('SELECT r, a, u FROM Entity\Role r LEFT JOIN r.actions a LEFT JOIN r.users u')
->getArrayResult();
@ -65,7 +45,7 @@ class PermissionsController extends BaseController
$this->view->roles = $roles;
}
public function editroleAction()
public function editAction()
{
$form = new \App\Form($this->current_module_config->forms->role->toArray());
@ -74,7 +54,7 @@ class PermissionsController extends BaseController
$record = $this->em->getRepository(Role::class)->find($this->getParam('id'));
$record_info = $record->toArray($this->em, true, true);
$actions = $this->em->getRepository(RoleHasAction::class)->getActionsForRole($record);
$actions = $this->em->getRepository(RolePermission::class)->getActionsForRole($record);
$form->setDefaults(array_merge($record_info, $actions));
}
@ -91,7 +71,7 @@ class PermissionsController extends BaseController
$this->em->persist($record);
$this->em->flush();
$this->em->getRepository(RoleHasAction::class)->setActionsForRole($record, $data);
$this->em->getRepository(RolePermission::class)->setActionsForRole($record, $data);
$this->alert('<b>'._('Record updated.').'</b>', 'green');
return $this->redirectFromHere(array('action' => 'index', 'id' => NULL, 'csrf' => NULL));
@ -100,7 +80,7 @@ class PermissionsController extends BaseController
return $this->renderForm($form, 'edit', _('Edit Record'));
}
public function deleteroleAction()
public function deleteAction()
{
$record = $this->em->getRepository(Role::class)->find($this->getParam('id'));
if ($record instanceof Role)

View File

@ -31,20 +31,14 @@ $app->group('/admin', function() {
$this->get('', 'admin:permissions:index')
->setName('admin:permissions:index');
$this->map(['GET', 'POST'], '/role/edit[/{id}]', 'admin:permissions:editrole')
->setName('admin:permissions:editrole');
$this->map(['GET', 'POST'], '/edit[/{id}]', 'admin:permissions:edit')
->setName('admin:permissions:edit');
$this->get('/role/delete/{id}', 'admin:permissions:deleterole')
->setName('admin:permissions:deleterole');
$this->get('/delete/{id}', 'admin:permissions:delete')
->setName('admin:permissions:delete');
$this->get('/role/members/{id}', 'admin:permissions:rolemembers')
->setName('admin:permissions:rolemembers');
$this->map(['GET', 'POST'], '/action/edit[/{id}]', 'admin:permissions:editaction')
->setName('admin:permissions:editaction');
$this->get('/action/delete/{id}', 'admin:permissions:deleteaction')
->setName('admin:permissions:deleteaction');
$this->get('/members/{id}', 'admin:permissions:members')
->setName('admin:permissions:members');
});

View File

@ -4,83 +4,53 @@
<h2><?=_('Manage Permissions') ?></h2>
</div>
<div class="row">
<div class="col-sm-6">
<div class="card">
<div class="card-header ch-alt">
<h2><?=_('Roles') ?></h2>
<a class="btn bgm-blue btn-float" href="<?=$url->routeFromHere(array('action' => 'editrole')) ?>"><i class="zmdi zmdi-plus"></i></a>
</div>
<div class="table-responsive">
<table class="table table-striped">
<colgroup>
<col width="40%" />
<col width="60%" />
</colgroup>
<thead>
<tr>
<th>&nbsp;</th>
<th><?=_('Role Name') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($roles as $role): ?>
<tr class="input">
<td class="center">
<a class="btn btn-sm btn-primary" href="<?=$url->routeFromHere(array('action' => 'editrole', 'id' => $role['id'])) ?>"><?=_('Edit') ?></a>
<?php if( $role['id'] != 1 ): ?>
<a class="btn btn-sm btn-danger" href="<?=$url->routeFromHere(array('action' => 'deleterole', 'id' => $role['id'])) ?>"><?=_('Delete') ?></a>
<?php else: ?>
<a class="btn btn-sm btn-danger disabled" href="#" onclick="alert('<?=_('This role cannot be deleted.') ?>'); return false;"><?=_('Delete') ?></a>
<?php endif; ?>
</td>
<td align="left"><?=$this->e($role['name']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header ch-alt">
<h2><?=_('Roles') ?></h2>
<a class="btn bgm-blue btn-float" href="<?=$url->routeFromHere(array('action' => 'edit')) ?>"><i class="zmdi zmdi-plus"></i></a>
</div>
<div class="col-sm-6">
<div class="card">
<div class="card-header ch-alt">
<h2><?=_('Actions') ?></h2>
<a class="btn bgm-blue btn-float" href="<?=$url->routeFromHere(array('action' => 'editaction')) ?>"><i class="zmdi zmdi-plus"></i></a>
</div>
<div class="table-responsive">
<table class="table table-striped">
<colgroup>
<col width="40%" />
<col width="60%" />
</colgroup>
<thead>
<tr>
<th>&nbsp;</th>
<th><?=_('Action Name') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($actions as $action): ?>
<tr class="input">
<td class="center">
<a class="btn btn-sm btn-primary" href="<?=$url->routeFromHere(array('action' => 'editaction', 'id' => $action['id'])) ?>"><?=_('Edit') ?></a>
<a class="btn btn-sm btn-danger" href="<?=$url->routeFromHere(array('action' => 'deleteaction', 'id' => $action['id'])) ?>"><?=_('Delete') ?></a>
</td>
<td align="left">
<?=$this->e($action['name']) ?><br><small>
<?php if ($action['is_global']): ?>
<?=_('System-Wide') ?>
<?php else: ?>
<?=_('Per-Station') ?>
<? endif; ?>
</small>
</td>
</tr>
<div class="table-responsive">
<table class="table table-striped">
<colgroup>
<col width="20%">
<col width="20%">
<col width="30%">
<col width="30%">
</colgroup>
<thead>
<tr>
<th><?=_('Actions') ?></th>
<th><?=_('Role Name') ?></th>
<th><?=_('System-Wide Permissions') ?></th>
<th><?=_('Per-Station Permissions') ?></th>
</tr>
</thead>
<tbody>
<?php foreach($roles as $role): ?>
<tr class="input">
<td class="center">
<a class="btn btn-sm btn-primary" href="<?=$url->routeFromHere(array('action' => 'edit', 'id' => $role['id'])) ?>"><?=_('Edit') ?></a>
<?php if( $role['id'] != 1 ): ?>
<a class="btn btn-sm btn-danger" href="<?=$url->routeFromHere(array('action' => 'delete', 'id' => $role['id'])) ?>"><?=_('Delete') ?></a>
<?php else: ?>
<a class="btn btn-sm btn-danger disabled" href="#" onclick="alert('<?=_('This role cannot be deleted.') ?>'); return false;"><?=_('Delete') ?></a>
<?php endif; ?>
</td>
<td>
<big><?=$this->e($role['name']) ?></big>
</td>
<td>
<?=implode($role['permissions_global'], ', ') ?>
</td>
<td>
<?php foreach($role['permissions_station'] as $station_name => $station_perms): ?>
<div><b><?=$station_name ?></b>: <?=implode($station_perms, ', ') ?></div>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>

View File

@ -0,0 +1,18 @@
<?php
/**
* Module-specific actions (permissions)
*/
return [
'global' => [
],
'station' => [
'view station management',
'view station reports',
'manage station profile',
'manage station broadcasting',
'manage station streamers',
'manage station media',
'manage station automation',
],
];