#1180 -- Greatly improve user self-deletion/role management protections.

This commit is contained in:
Buster "Silver Eagle" Neece 2019-02-17 18:52:01 -06:00
parent 722dce7785
commit e2c73c13fd
19 changed files with 507 additions and 269 deletions

8
composer.lock generated
View File

@ -94,12 +94,12 @@
"source": {
"type": "git",
"url": "https://github.com/AzuraCast/azuracore.git",
"reference": "ecf6362ae7ee8403f626b9fcfac0e6767f00378d"
"reference": "ddfbbc6586b8d3cb4c72f8cf6edf678b9cadb6ba"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/AzuraCast/azuracore/zipball/ecf6362ae7ee8403f626b9fcfac0e6767f00378d",
"reference": "ecf6362ae7ee8403f626b9fcfac0e6767f00378d",
"url": "https://api.github.com/repos/AzuraCast/azuracore/zipball/ddfbbc6586b8d3cb4c72f8cf6edf678b9cadb6ba",
"reference": "ddfbbc6586b8d3cb4c72f8cf6edf678b9cadb6ba",
"shasum": ""
},
"require": {
@ -149,7 +149,7 @@
}
],
"description": "A lightweight core application framework.",
"time": "2019-02-05T17:08:40+00:00"
"time": "2019-02-17T22:53:12+00:00"
},
{
"name": "azuracast/azuraforms",

View File

@ -46,6 +46,7 @@ return function (\Azura\EventDispatcher $dispatcher)
// User-side tools
new Command\ResetPassword,
new Command\SetAdministrator,
new Command\ListSettings,
new Command\SetSetting,
]);

View File

@ -5,69 +5,47 @@ $actions = \App\Acl::listPermissions();
$form_config = [
'method' => 'post',
'groups' => [
'elements' => [
'basic_info' => [
'elements' => [
'name' => [
'text',
[
'label' => __('Role Name'),
'class' => 'half-width',
'required' => true,
]
],
],
'name' => [
'text',
[
'label' => __('Role Name'),
'class' => 'half-width',
'required' => true,
]
],
'grp_global' => [
'legend' => __('System-Wide Permissions'),
'elements' => [
'actions_global' => [
'multiSelect',
[
'label' => __('Actions'),
'choices' => $actions['global'],
]
],
],
'actions_global' => [
'multiSelect',
[
'label' => __('System-Wide Permissions'),
'choices' => $actions['global'],
'class' => 'permission-select',
]
],
],
];
foreach ($all_stations as $station) {
$form_config['groups']['grp_station_' . $station['id']] = [
'legend' => __('Per-Station').': '.$station['name'],
'elements' => [
'actions_' . $station['id'] => [
'multiSelect',
[
'label' => __('Actions'),
'choices' => $actions['station'],
]
],
],
$form_config['elements']['actions_' . $station['id']] = [
'multiSelect',
[
'label' => __('Permissions for %s', $station['name']),
'choices' => $actions['station'],
'class' => 'permission-select',
]
];
}
$form_config['groups']['grp_submit'] = [
'elements' => [
'submit' => [
'submit',
[
'type' => 'submit',
'label' => __('Save Changes'),
'class' => 'btn btn-lg btn-primary',
]
],
],
$form_config['elements']['submit'] = [
'submit',
[
'type' => 'submit',
'label' => __('Save Changes'),
'class' => 'btn btn-lg btn-primary',
]
];
return $form_config;

View File

@ -263,27 +263,6 @@ return function (\Azura\Container $di)
);
};
$di[\App\Form\EntityForm::class] = function($di) {
return new \App\Form\EntityForm(
$di[\Doctrine\ORM\EntityManager::class],
$di[\Symfony\Component\Serializer\Serializer::class],
$di[\Symfony\Component\Validator\Validator\ValidatorInterface::class]
);
};
$di[\App\Form\StationForm::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
return new \App\Form\StationForm(
$di[\Doctrine\ORM\EntityManager::class],
$di[\Symfony\Component\Serializer\Serializer::class],
$di[\Symfony\Component\Validator\Validator\ValidatorInterface::class],
$di[\App\Acl::class],
$config->get('forms/station')
);
};
// Radio management
$di->register(new \App\Provider\RadioProvider);
@ -299,6 +278,9 @@ return function (\Azura\Container $di)
// Notifications
$di->register(new \App\Provider\NotificationProvider);
// Forms
$di->register(new \App\Provider\FormProvider);
// Controller groups
$di->register(new \App\Provider\AdminProvider);
$di->register(new \App\Provider\ApiProvider);

View File

@ -3,28 +3,28 @@ namespace App;
class Acl
{
const GLOBAL_ALL = 'administer all';
const GLOBAL_VIEW = 'view administration';
const GLOBAL_LOGS = 'view system logs';
const GLOBAL_SETTINGS = 'administer settings';
const GLOBAL_API_KEYS = 'administer api keys';
const GLOBAL_USERS = 'administer user accounts';
const GLOBAL_PERMISSIONS = 'administer permissions';
const GLOBAL_STATIONS = 'administer stations';
const GLOBAL_CUSTOM_FIELDS = 'administer custom fields';
public const GLOBAL_ALL = 'administer all';
public const GLOBAL_VIEW = 'view administration';
public const GLOBAL_LOGS = 'view system logs';
public const GLOBAL_SETTINGS = 'administer settings';
public const GLOBAL_API_KEYS = 'administer api keys';
public const GLOBAL_USERS = 'administer user accounts';
public const GLOBAL_PERMISSIONS = 'administer permissions';
public const GLOBAL_STATIONS = 'administer stations';
public const GLOBAL_CUSTOM_FIELDS = 'administer custom fields';
const STATION_ALL = 'administer all';
const STATION_VIEW = 'view station management';
const STATION_REPORTS = 'view station reports';
const STATION_LOGS = 'view station logs';
const STATION_PROFILE = 'manage station profile';
const STATION_BROADCASTING = 'manage station broadcasting';
const STATION_STREAMERS = 'manage station streamers';
const STATION_MOUNTS = 'manage station mounts';
const STATION_REMOTES = 'manage station remotes';
const STATION_MEDIA = 'manage station media';
const STATION_AUTOMATION = 'manage station automation';
const STATION_WEB_HOOKS = 'manage station web hooks';
public const STATION_ALL = 'administer all';
public const STATION_VIEW = 'view station management';
public const STATION_REPORTS = 'view station reports';
public const STATION_LOGS = 'view station logs';
public const STATION_PROFILE = 'manage station profile';
public const STATION_BROADCASTING = 'manage station broadcasting';
public const STATION_STREAMERS = 'manage station streamers';
public const STATION_MOUNTS = 'manage station mounts';
public const STATION_REMOTES = 'manage station remotes';
public const STATION_MEDIA = 'manage station media';
public const STATION_AUTOMATION = 'manage station automation';
public const STATION_WEB_HOOKS = 'manage station web hooks';
/** @var Entity\Repository\RolePermissionRepository */
protected $permission_repo;
@ -85,9 +85,10 @@ class Acl
*
* @param int|array $role_id
* @param string|array $action
* @param int|null $station_id
* @return bool
*/
public function roleAllowed($role_id, $action, $station_id = null)
public function roleAllowed($role_id, $action, $station_id = null): bool
{
// Iterate through an array of roles and return with the first "true" response, or "false" otherwise.
if (\is_array($role_id)) {
@ -114,17 +115,17 @@ class Acl
if (!empty($this->_actions[$role_id])) {
$role_actions = (array)$this->_actions[$role_id];
if (\in_array('administer all', (array)$role_actions['global'], true)) {
if (\in_array(self::GLOBAL_ALL, (array)$role_actions['global'], true)) {
return true;
}
if ($station_id !== null) {
if (\in_array('administer stations', (array)$role_actions['global'], true)) {
if (\in_array(self::GLOBAL_STATIONS, (array)$role_actions['global'], true)) {
return true;
}
if (!empty($role_actions['stations'][$station_id])) {
if (\in_array('administer all', $role_actions['stations'][$station_id], true)) {
if (\in_array(self::STATION_ALL, $role_actions['stations'][$station_id], true)) {
return true;
}
@ -147,7 +148,7 @@ class Acl
* @throws Exception\NotLoggedIn
* @throws Exception\PermissionDenied
*/
public function checkPermission(?Entity\User $user = null, $action, $station_id = null)
public function checkPermission(?Entity\User $user = null, $action, $station_id = null): void
{
if (!($user instanceof Entity\User)) {
throw new Exception\NotLoggedIn;
@ -171,26 +172,26 @@ class Acl
self::GLOBAL_ALL => __('All Permissions'),
self::GLOBAL_VIEW => __('View Administration Page'),
self::GLOBAL_LOGS => __('View System Logs'),
self::GLOBAL_SETTINGS => sprintf(__('Administer %s'), __('Settings')),
self::GLOBAL_API_KEYS => sprintf(__('Administer %s'), __('API Keys')),
self::GLOBAL_USERS => sprintf(__('Administer %s'), __('Users')),
self::GLOBAL_PERMISSIONS => sprintf(__('Administer %s'), __('Permissions')),
self::GLOBAL_STATIONS => sprintf(__('Administer %s'), __('Stations')),
self::GLOBAL_CUSTOM_FIELDS => sprintf(__('Administer %s'), __('Custom Fields')),
self::GLOBAL_SETTINGS => __('Administer %s', __('Settings')),
self::GLOBAL_API_KEYS => __('Administer %s', __('API Keys')),
self::GLOBAL_USERS => __('Administer %s', __('Users')),
self::GLOBAL_PERMISSIONS => __('Administer %s', __('Permissions')),
self::GLOBAL_STATIONS => __('Administer %s', __('Stations')),
self::GLOBAL_CUSTOM_FIELDS => __('Administer %s', __('Custom Fields')),
],
'station' => [
self::STATION_ALL => __('All Permissions'),
self::STATION_VIEW => __('View Station Page'),
self::STATION_REPORTS => __('View Station Reports'),
self::STATION_LOGS => __('View Station Logs'),
self::STATION_PROFILE => sprintf(__('Manage Station %s'), __('Profile')),
self::STATION_BROADCASTING => sprintf(__('Manage Station %s'), __('Broadcasting')),
self::STATION_STREAMERS => sprintf(__('Manage Station %s'), __('Streamers')),
self::STATION_MOUNTS => sprintf(__('Manage Station %s'), __('Mount Points')),
self::STATION_REMOTES => sprintf(__('Manage Station %s'), __('Remote Relays')),
self::STATION_MEDIA => sprintf(__('Manage Station %s'), __('Media')),
self::STATION_AUTOMATION => sprintf(__('Manage Station %s'), __('Automation')),
self::STATION_WEB_HOOKS => sprintf(__('Manage Station %s'), __('Web Hooks')),
self::STATION_PROFILE => __('Manage Station %s', __('Profile')),
self::STATION_BROADCASTING => __('Manage Station %s', __('Broadcasting')),
self::STATION_STREAMERS => __('Manage Station %s', __('Streamers')),
self::STATION_MOUNTS => __('Manage Station %s', __('Mount Points')),
self::STATION_REMOTES => __('Manage Station %s', __('Remote Relays')),
self::STATION_MEDIA => __('Manage Station %s', __('Media')),
self::STATION_AUTOMATION => __('Manage Station %s', __('Automation')),
self::STATION_WEB_HOOKS => __('Manage Station %s', __('Web Hooks')),
]
];
}

View File

@ -58,9 +58,9 @@ class ResetPassword extends CommandAbstract
'',
]);
return 0;
} else {
$io->error('Account not found.');
return 1;
}
$io->error('Account not found.');
return 1;
}
}

View File

@ -0,0 +1,76 @@
<?php
namespace App\Console\Command;
use App\Acl;
use App\Entity;
use Azura\Console\Command\CommandAbstract;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
class SetAdministrator extends CommandAbstract
{
/**
* {@inheritdoc}
*/
protected function configure()
{
$this->setName('azuracast:account:set-administrator')
->setDescription('Set the account specified as a global administrator.')
->addArgument(
'email',
InputArgument::REQUIRED,
'The user\'s e-mail address.'
);
}
/**
* {@inheritdoc}
*/
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->title('Set Administrator');
/** @var EntityManager $em */
$em = $this->get(EntityManager::class);
$user_email = $input->getArgument('email');
$user = $em->getRepository(Entity\User::class)
->findOneBy(['email' => $user_email ]);
if ($user instanceof Entity\User) {
$admin_role = $em->getRepository(Entity\Role::class)
->find(Entity\Role::SUPER_ADMINISTRATOR_ROLE_ID);
/** @var Entity\Repository\RolePermissionRepository $perms_repo */
$perms_repo = $em->getRepository(Entity\RolePermission::class);
$perms_repo->setActionsForRole($admin_role, [
'actions_global' => [
Acl::GLOBAL_ALL,
]
]);
$user_roles = $user->getRoles();
if (!$user_roles->contains($admin_role)) {
$user_roles->add($admin_role);
}
$em->persist($user);
$em->flush();
$io->text(__('The account associated with e-mail address "%s" has been set as an administrator', $user->getEmail()));
$io->newLine();
return 0;
}
$io->error(__('Account not found.'));
$io->newLine();
return 1;
}
}

View File

@ -2,6 +2,7 @@
namespace App\Controller\Admin;
use App\Acl;
use App\Form\PermissionsForm;
use Doctrine\ORM\EntityManager;
use App\Entity;
use App\Http\Request;
@ -13,24 +14,24 @@ class PermissionsController
/** @var EntityManager */
protected $em;
/** @var array */
protected $actions;
/** @var array */
protected $form_config;
/** @var PermissionsForm */
protected $form;
/** @var string */
protected $csrf_namespace = 'admin_permissions';
/**
* @param EntityManager $em
* @param array $form_config
* @param PermissionsForm $form
*
* @see \App\Provider\AdminProvider
*/
public function __construct(EntityManager $em, array $form_config)
public function __construct(
EntityManager $em,
PermissionsForm $form)
{
$this->em = $em;
$this->form_config = $form_config;
$this->form = $form;
}
public function indexAction(Request $request, Response $response): ResponseInterface
@ -70,43 +71,18 @@ class PermissionsController
/** @var \Azura\Doctrine\Repository $role_repo */
$role_repo = $this->em->getRepository(Entity\Role::class);
/** @var Entity\Repository\RolePermissionRepository $permission_repo */
$permission_repo = $this->em->getRepository(Entity\RolePermission::class);
$form = new \AzuraForms\Form($this->form_config);
if (!empty($id)) {
$record = $role_repo->find($id);
$record_info = $role_repo->toArray($record, true, true);
$actions = $permission_repo->getActionsForRole($record);
$form->populate(array_merge($record_info, $actions));
} else {
$record = null;
}
if (!empty($_POST) && $form->isValid($_POST)) {
$data = $form->getValues();
if (!($record instanceof Entity\Role)) {
$record = new Entity\Role;
}
$role_repo->fromArray($record, $data);
$this->em->persist($record);
$this->em->flush();
$permission_repo->setActionsForRole($record, $data);
$record = (null !== $id)
? $role_repo->find((int)$id)
: null;
if (false !== $this->form->process($request, $record)) {
$request->getSession()->flash('<b>' . sprintf(($id) ? __('%s updated.') : __('%s added.'), __('Permission')) . '</b>', 'green');
return $response->withRedirect($request->getRouter()->named('admin:permissions:index'));
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $form,
'form' => $this->form,
'render_mode' => 'edit',
'title' => sprintf(($id) ? __('Edit %s') : __('Add %s'), __('Permission')),
]);

View File

@ -26,7 +26,7 @@ class StationsController
public function __construct(Form\StationForm $station_form)
{
$this->station_form = $station_form;
$this->record_repo = $station_form->getStationRepository();
$this->record_repo = $station_form->getEntityRepository();
}
public function __invoke(Request $request, Response $response): ResponseInterface

View File

@ -2,6 +2,7 @@
namespace App\Controller\Admin;
use App\Auth;
use App\Form\UserForm;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManager;
use App\Entity;
@ -14,14 +15,14 @@ class UsersController
/** @var EntityManager */
protected $em;
/** @var Entity\Repository\UserRepository */
protected $record_repo;
/** @var Auth */
protected $auth;
/** @var array */
protected $form_config;
/** @var Entity\Repository\UserRepository */
protected $record_repo;
/** @var UserForm */
protected $form;
/** @var string */
protected $csrf_namespace = 'admin_users';
@ -29,14 +30,18 @@ class UsersController
/**
* @param EntityManager $em
* @param Auth $auth
* @param array $form_config
* @param UserForm $form
*
* @see \App\Provider\AdminProvider
*/
public function __construct(EntityManager $em, Auth $auth, array $form_config)
public function __construct(
EntityManager $em,
Auth $auth,
UserForm $form)
{
$this->em = $em;
$this->auth = $auth;
$this->form_config = $form_config;
$this->form = $form;
$this->record_repo = $this->em->getRepository(Entity\User::class);
}
@ -55,42 +60,23 @@ class UsersController
public function editAction(Request $request, Response $response, $id = null): ResponseInterface
{
$form = new \AzuraForms\Form($this->form_config);
$record = (null !== $id)
? $this->record_repo->find((int)$id)
: null;
if (!empty($id)) {
$record = $this->record_repo->find((int)$id);
$record_defaults = $this->record_repo->toArray($record, true, true);
unset($record_defaults['auth_password']);
$form->populate($record_defaults);
} else {
$record = null;
}
if (!empty($_POST) && $form->isValid($_POST)) {
$data = $form->getValues();
if (!($record instanceof Entity\User)) {
$record = new Entity\User;
}
$this->record_repo->fromArray($record, $data);
try {
$this->em->persist($record);
$this->em->flush();
$request->getSession()->flash(sprintf(($id) ? __('%s updated.') : __('%s added.'), __('User')), 'green');
try {
if (false !== $this->form->process($request, $record)) {
$request->getSession()->flash(sprintf(($id) ? __('%s updated.') : __('%s added.'), __('User')),
'green');
return $response->withRedirect($request->getRouter()->named('admin:users:index'));
} catch(UniqueConstraintViolationException $e) {
$request->getSession()->flash(__('Another user already exists with this e-mail address. Please update the e-mail address.'), 'red');
}
} catch(UniqueConstraintViolationException $e) {
$request->getSession()->flash(__('Another user already exists with this e-mail address. Please update the e-mail address.'), 'red');
}
return $request->getView()->renderToResponse($response, 'system/form_page', [
'form' => $form,
'form' => $this->form,
'render_mode' => 'edit',
'title' => sprintf(($id) ? __('Edit %s') : __('Add %s'), __('User'))
]);
@ -102,14 +88,17 @@ class UsersController
$user = $this->record_repo->find((int)$id);
if ($user instanceof Entity\User) {
$current_user = $request->getUser();
if ($user === $current_user) {
$request->getSession()->flash('<b>'.__('You cannot delete your own account.').'</b>', 'red');
} elseif ($user instanceof Entity\User) {
$this->em->remove($user);
$this->em->flush();
$request->getSession()->flash('<b>' . __('%s deleted.', __('User')) . '</b>', 'green');
}
$this->em->flush();
$request->getSession()->flash('<b>' . __('%s deleted.', __('User')) . '</b>', 'green');
return $response->withRedirect($request->getRouter()->named('admin:users:index'));
}

View File

@ -15,6 +15,8 @@ use Symfony\Component\Validator\Constraints as Assert;
*/
class Role implements \JsonSerializable
{
public const SUPER_ADMINISTRATOR_ROLE_ID = 1;
use Traits\TruncateStrings;
/**

View File

@ -2,10 +2,12 @@
namespace App\Form;
use App\Http\Request;
use Azura\Doctrine\Repository;
use Azura\Normalizer\DoctrineEntityNormalizer;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\Validator\Validator\ValidatorInterface;
/**
@ -46,14 +48,6 @@ class EntityForm extends \AzuraForms\Form
$this->validator = $validator;
}
/**
* @return EntityManager
*/
public function getEntityManager(): EntityManager
{
return $this->em;
}
/**
* @return string
*/
@ -70,6 +64,26 @@ class EntityForm extends \AzuraForms\Form
$this->entityClass = $entityClass;
}
/**
* @return EntityManager
*/
public function getEntityManager(): EntityManager
{
return $this->em;
}
/**
* @return Repository
*/
public function getEntityRepository(): Repository
{
if (null === $this->entityClass) {
throw new \Azura\Exception('Entity class name is not specified.');
}
return $this->em->getRepository($this->entityClass);
}
/**
* @param Request $request
* @param object|null $record
@ -77,7 +91,11 @@ class EntityForm extends \AzuraForms\Form
*/
public function process(Request $request, $record = null)
{
if (!($record instanceof $this->entityClass)) {
if (null === $this->entityClass) {
throw new \Azura\Exception('Entity class name is not specified.');
}
if (null !== $record && !($record instanceof $this->entityClass)) {
throw new \InvalidArgumentException(sprintf('Record must be an instance of %s.', $this->entityClass));
}
@ -94,9 +112,25 @@ class EntityForm extends \AzuraForms\Form
$errors = $this->validator->validate($record);
if (count($errors) > 0) {
$e = new \App\Exception\Validation((string)$errors);
$e->setDetailedErrors($errors);
throw $e;
$other_errors = [];
foreach($errors as $error) {
/** @var ConstraintViolation $error */
$field_name = $error->getPropertyPath();
if (isset($this->fields[$field_name])) {
$this->fields[$field_name]->addError($error->getMessage());
} else {
$other_errors[] = $error;
}
}
if (count($other_errors) > 0) {
$e = new \App\Exception\Validation((string)$errors);
$e->setDetailedErrors($errors);
throw $e;
}
return false;
}
$this->em->persist($record);
@ -110,9 +144,9 @@ class EntityForm extends \AzuraForms\Form
/**
* @param $record
* @param array $context
* @return array|bool|float|int|mixed|string
* @return array
*/
protected function _normalizeRecord($record, array $context = [])
protected function _normalizeRecord($record, array $context = []): array
{
return $this->serializer->normalize($record, null, array_merge($context, [
DoctrineEntityNormalizer::NORMALIZE_TO_IDENTIFIERS => true,

View File

@ -0,0 +1,95 @@
<?php
namespace App\Form;
use App\Entity;
use App\Http\Request;
use Azura\Doctrine\Repository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class PermissionsForm extends EntityForm
{
/** @var Entity\Repository\RolePermissionRepository */
protected $permissions_repo;
/** @var bool */
protected $set_permissions = true;
/**
* @param EntityManager $em
* @param Serializer $serializer
* @param ValidatorInterface $validator
* @param array $form_config
*/
public function __construct(
EntityManager $em,
Serializer $serializer,
ValidatorInterface $validator,
array $form_config)
{
parent::__construct($em, $serializer, $validator, $form_config);
$this->entityClass = Entity\Role::class;
$this->permissions_repo = $em->getRepository(Entity\RolePermission::class);
}
/**
* @inheritdoc
*/
public function getEntityRepository(): Repository
{
return $this->permissions_repo;
}
/**
* @inheritdoc
*/
public function process(Request $request, $record = null)
{
if ($record instanceof Entity\Role && Entity\Role::SUPER_ADMINISTRATOR_ROLE_ID === $record->getId()) {
$this->set_permissions = false;
foreach($this->fields as $field_id => $field) {
$attrs = $field->getAttributes();
if (isset($attrs['class']) && strpos($attrs['class'], 'permission-select') !== false) {
unset($this->fields[$field_id]);
}
}
}
return parent::process($request, $record);
}
/**
* @inheritdoc
*/
protected function _denormalizeToRecord($data, $record = null, array $context = []): object
{
$record = parent::_denormalizeToRecord($data, $record, $context);
if ($this->set_permissions) {
$this->em->persist($record);
$this->em->flush();
$this->permissions_repo->setActionsForRole($record, $data);
}
return $record;
}
/**
* @inheritdoc
*/
protected function _normalizeRecord($record, array $context = []): array
{
$data = parent::_normalizeRecord($record, $context);
if ($this->set_permissions) {
$actions = $this->permissions_repo->getActionsForRole($record);
return array_merge($data, $actions);
}
return $data;
}
}

View File

@ -4,6 +4,7 @@ namespace App\Form;
use App\Acl;
use App\Entity;
use App\Http\Request;
use Azura\Doctrine\Repository;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
@ -41,9 +42,9 @@ class StationForm extends EntityForm
}
/**
* @return Entity\Repository\StationRepository
* @inheritdoc
*/
public function getStationRepository(): Entity\Repository\StationRepository
public function getEntityRepository(): Repository
{
return $this->station_repo;
}

43
src/Form/UserForm.php Normal file
View File

@ -0,0 +1,43 @@
<?php
namespace App\Form;
use App\Entity;
use App\Http\Request;
use Doctrine\ORM\EntityManager;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class UserForm extends EntityForm
{
/**
* @param EntityManager $em
* @param Serializer $serializer
* @param ValidatorInterface $validator
* @param array $form_config
*/
public function __construct(
EntityManager $em,
Serializer $serializer,
ValidatorInterface $validator,
array $form_config)
{
parent::__construct($em, $serializer, $validator, $form_config);
$this->entityClass = Entity\User::class;
}
/**
* @inheritdoc
*/
public function process(Request $request, $record = null)
{
// Check for administrative permissions and hide admin fields otherwise.
$user = $request->getUser();
if ($record instanceof Entity\User && $record->getId() === $user->getId()) {
unset($this->fields['roles']);
}
return parent::process($request, $record);
}
}

View File

@ -2,6 +2,7 @@
namespace App\Provider;
use App\Controller\Admin;
use Doctrine\ORM\EntityManager;
use Pimple\ServiceProviderInterface;
use Pimple\Container;
use App\Entity;
@ -15,7 +16,7 @@ class AdminProvider implements ServiceProviderInterface
$config = $di[\Azura\Config::class];
return new Admin\ApiController(
$di[\Doctrine\ORM\EntityManager::class],
$di[EntityManager::class],
$config->get('forms/api_key')
);
};
@ -35,7 +36,7 @@ class AdminProvider implements ServiceProviderInterface
$config = $di[\Azura\Config::class];
return new Admin\CustomFieldsController(
$di[\Doctrine\ORM\EntityManager::class],
$di[EntityManager::class],
$config->get('forms/custom_field')
);
};
@ -50,25 +51,14 @@ class AdminProvider implements ServiceProviderInterface
$di[Admin\LogsController::class] = function($di) {
return new Admin\LogsController(
$di[\Doctrine\ORM\EntityManager::class]
$di[EntityManager::class]
);
};
$di[Admin\PermissionsController::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
/** @var \Doctrine\ORM\EntityManager $em */
$em = $di[\Doctrine\ORM\EntityManager::class];
/** @var Entity\Repository\StationRepository $stations_repo */
$stations_repo = $em->getRepository(Entity\Station::class);
return new Admin\PermissionsController(
$em,
$config->get('forms/role', [
'all_stations' => $stations_repo->fetchArray(),
])
$di[EntityManager::class],
$di[\App\Form\PermissionsForm::class]
);
};
@ -93,7 +83,7 @@ class AdminProvider implements ServiceProviderInterface
$config = $di[\Azura\Config::class];
return new Admin\Stations\CloneController(
$di[\Doctrine\ORM\EntityManager::class],
$di[EntityManager::class],
$di[\Azura\Cache::class],
$di[\App\Radio\Configuration::class],
$config->get('forms/station_clone')
@ -101,21 +91,10 @@ class AdminProvider implements ServiceProviderInterface
};
$di[Admin\UsersController::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
/** @var \Doctrine\ORM\EntityManager $em */
$em = $di[\Doctrine\ORM\EntityManager::class];
/** @var \Azura\Doctrine\Repository $role_repo */
$role_repo = $em->getRepository(Entity\Role::class);
return new Admin\UsersController(
$di[\Doctrine\ORM\EntityManager::class],
$di[EntityManager::class],
$di[\App\Auth::class],
$config->get('forms/user', [
'roles' => $role_repo->fetchSelect()
])
$di[\App\Form\UserForm::class]
);
};

View File

@ -0,0 +1,79 @@
<?php
namespace App\Provider;
use App\Form;
use App\Entity;
use Doctrine\ORM\EntityManager;
use Pimple\ServiceProviderInterface;
use Pimple\Container;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Validator\Validator\ValidatorInterface;
class FormProvider implements ServiceProviderInterface
{
public function register(Container $di)
{
$di[Form\EntityForm::class] = function($di) {
return new Form\EntityForm(
$di[EntityManager::class],
$di[Serializer::class],
$di[ValidatorInterface::class]
);
};
$di[Form\PermissionsForm::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
/** @var \Doctrine\ORM\EntityManager $em */
$em = $di[EntityManager::class];
/** @var Entity\Repository\StationRepository $stations_repo */
$stations_repo = $em->getRepository(Entity\Station::class);
return new Form\PermissionsForm(
$di[EntityManager::class],
$di[Serializer::class],
$di[ValidatorInterface::class],
$config->get('forms/role', [
'all_stations' => $stations_repo->fetchArray(),
])
);
};
$di[Form\StationForm::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
return new Form\StationForm(
$di[EntityManager::class],
$di[Serializer::class],
$di[ValidatorInterface::class],
$di[\App\Acl::class],
$config->get('forms/station')
);
};
$di[Form\UserForm::class] = function($di) {
/** @var \Azura\Config $config */
$config = $di[\Azura\Config::class];
/** @var \Doctrine\ORM\EntityManager $em */
$em = $di[EntityManager::class];
/** @var \Azura\Doctrine\Repository $role_repo */
$role_repo = $em->getRepository(Entity\Role::class);
return new Form\UserForm(
$di[EntityManager::class],
$di[Serializer::class],
$di[ValidatorInterface::class],
$config->get('forms/user', [
'roles' => $role_repo->fetchSelect()
])
);
};
}
}

View File

@ -13,17 +13,15 @@
<div class="table-responsive">
<table class="table table-striped">
<colgroup>
<col width="20%">
<col width="20%">
<col width="30%">
<col width="30%">
<col width="50%">
</colgroup>
<thead>
<tr>
<th><?=__('Actions') ?></th>
<th><?=__('Role Name') ?></th>
<th><?=__('System-Wide Permissions') ?></th>
<th><?=__('Per-Station Permissions') ?></th>
<th><?=__('Permissions') ?></th>
</tr>
</thead>
<tbody>
@ -39,15 +37,15 @@
<?php endif; ?>
</td>
<td>
<big><?=$this->e($role['name']) ?></big>
<div class="typography-subheading"><?=$this->e($role['name']) ?></div>
</td>
<td>
<?=implode($role['permissions_global'], ', ') ?>
</td>
<td>
<?php foreach($role['permissions_station'] as $station_name => $station_perms): ?>
<div><b><?=$this->e($station_name) ?></b>: <?=implode($station_perms, ', ') ?></div>
<?php endforeach; ?>
<?php if (!empty($role['permissions_global'])): ?>
<div><b><?=__('Global') ?>:</b> <?=implode($role['permissions_global'], ', ') ?></div>
<?php endif; ?>
<?php foreach($role['permissions_station'] as $station_name => $station_perms): ?>
<div><b><?=$this->e($station_name) ?></b>: <?=implode($station_perms, ', ') ?></div>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>

View File

@ -14,8 +14,8 @@
<table class="table table-striped">
<colgroup>
<col width="30%">
<col width="40%">
<col width="30%">
<col width="35%">
<col width="35%">
</colgroup>
<thead>
<tr>
@ -33,19 +33,23 @@
<a class="btn btn-sm btn-primary" href="<?=$router->named('admin:users:impersonate', ['id' => $user_row->getId(), 'csrf' => $csrf]) ?>"><?=__('Log In') ?></a>
<?php endif; ?>
<a class="btn btn-sm btn-default" href="<?=$router->named('admin:users:edit', ['id' => $user_row->getId()]) ?>"><?=__('Edit') ?></a>
<?php if ($user_row->getId() !== $user->getId()): ?>
<a class="btn btn-sm btn-danger" data-confirm-title="<?=$this->e(__('Delete user "%s"?', $user_row->getEmail())) ?>" href="<?=$router->named('admin:users:delete', ['id' => $user_row->getId(), 'csrf' => $csrf]) ?>"><?=__('Delete') ?></a>
<?php else: ?>
<a class="btn btn-sm btn-danger disabled" href=""><?=__('Delete') ?></a>
<?php endif; ?>
</td>
<td><?=$this->mailto($user_row->getEmail()) ?> <?php if ($user_row->getId() === $user->getId()): ?><?=__('(You)') ?><?php endif; ?></td>
<td>
<small>
<?php
if (count($user_row->getRoles()) > 0)
{
foreach($user_row->getRoles() as $role)
echo '<div>'.$this->e($role->getName()).'</div>';
}
?>
</small>
<div class="text-lg-left"><?=$this->e($user_row->getName()) ?></div>
<div>
<?=$this->mailto($user_row->getEmail()) ?>
<?php if ($user_row->getId() === $user->getId()): ?><?=__('(You)') ?><?php endif; ?>
</div>
</td>
<td>
<?php foreach($user_row->getRoles() as $role): ?>
<div><?=$this->e($role->getName()) ?></div>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>