refactored permissions for simpler customizations (#808)
This commit is contained in:
@@ -28,10 +28,16 @@ New permissions are available. If you configured custom permissions in `local.ya
|
||||
- `view_tag` - view all tags
|
||||
- `delete_tag` - delete tags
|
||||
- `edit_exported_timesheet` - allows to edit records which were exported
|
||||
- `role_permissions` - view calculated permissions for user roles
|
||||
|
||||
Removed permission:
|
||||
- `system_actions` - removed experimental feature to flush your cache from the about screen
|
||||
|
||||
### BC BREAKS
|
||||
|
||||
- API: Format for queries including a datetime object fixed, finally using the HTML5 format (previously `2019-03-02 14:23` - now `2019-03-02T14:23:00`)
|
||||
- **Permission config**: the `permissions` definition in your `local.yaml` needs to be verified/changed, as the internal structure was highly optimized to simplify the definition.
|
||||
Thanks to the new structure, you should be able to remove almost everything from your `local.yaml`: please read [the updated permission docu](https://www.kimai.org/documentation/permissions.html).
|
||||
|
||||
## [0.9](https://github.com/kevinpapst/kimai2/releases/tag/0.9)
|
||||
|
||||
|
||||
@@ -39,8 +39,10 @@ export default class KimaiAPILink extends KimaiClickHandlerReducedInTableRow {
|
||||
}
|
||||
|
||||
if (attributes.question !== undefined) {
|
||||
self.getContainer().getPlugin('alert').question(attributes.question, function() {
|
||||
self.getContainer().getPlugin('alert').question(attributes.question, function(value) {
|
||||
if (value) {
|
||||
self._callApi(url, attributes);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
self._callApi(url, attributes);
|
||||
|
||||
@@ -41,8 +41,6 @@ export default class KimaiAlert extends KimaiPlugin {
|
||||
}
|
||||
|
||||
Swal.fire({
|
||||
//toast: true,
|
||||
//timer: 3000,
|
||||
timer: 1500,
|
||||
position: 'top-end',
|
||||
showConfirmButton: false,
|
||||
@@ -71,9 +69,7 @@ export default class KimaiAlert extends KimaiPlugin {
|
||||
confirmButtonText: translation.get('confirm'),
|
||||
cancelButtonText: translation.get('cancel')
|
||||
}).then((result) => {
|
||||
if (result.value) {
|
||||
callback(result.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -75,34 +75,38 @@ kimai:
|
||||
# https://www.kimai.org/documentation/permissions.html
|
||||
# --------------------------------------------------------------------------------
|
||||
permissions:
|
||||
# mapping complex rule sets of single permissions to named "sets" ("set name" = [array of "permissions and sets"])
|
||||
sets:
|
||||
# mapping complex rule sets of single permissions to named "sets" ("set name" = [array of "permissions"])
|
||||
ACTIVITIES: [view_activity,create_activity,edit_activity,delete_activity]
|
||||
PROJECTS: [view_project,create_project,edit_project,delete_project]
|
||||
CUSTOMERS: [view_customer,create_customer,edit_customer,delete_customer]
|
||||
INVOICE: [view_invoice,create_invoice]
|
||||
INVOICE_TEMPLATE: [view_invoice_template,create_invoice_template,edit_invoice_template,delete_invoice_template]
|
||||
TIMESHEET: [view_own_timesheet,start_own_timesheet,stop_own_timesheet,create_own_timesheet,edit_own_timesheet,export_own_timesheet,delete_own_timesheet]
|
||||
TIMESHEET_OTHER: [view_other_timesheet,start_other_timesheet,stop_other_timesheet,create_other_timesheet,edit_other_timesheet,export_other_timesheet,delete_other_timesheet]
|
||||
PROFILE: [view_own_profile,edit_own_profile,password_own_profile,preferences_own_profile,api-token_own_profile]
|
||||
PROFILE_OTHER: [view_other_profile,edit_other_profile,delete_other_profile,password_other_profile,roles_other_profile,preferences_other_profile,api-token_other_profile]
|
||||
TAGS: [view_tag,delete_tag]
|
||||
USER: [view_user,create_user,delete_user]
|
||||
RATE: [view_rate_own_timesheet,edit_rate_own_timesheet]
|
||||
RATE_OTHER: [view_rate_other_timesheet,edit_rate_other_timesheet]
|
||||
EXPORT: [view_export,create_export,edit_export_own_timesheet,edit_export_other_timesheet]
|
||||
maps:
|
||||
ACTIVITIES: ['view_activity','create_activity','edit_activity','delete_activity']
|
||||
PROJECTS: ['view_project','create_project','edit_project','delete_project']
|
||||
CUSTOMERS: ['view_customer','create_customer','edit_customer','delete_customer']
|
||||
INVOICE: ['view_invoice','create_invoice']
|
||||
INVOICE_TEMPLATE: ['view_invoice_template','create_invoice_template','edit_invoice_template','delete_invoice_template']
|
||||
TIMESHEET: ['view_own_timesheet','start_own_timesheet','stop_own_timesheet','create_own_timesheet','edit_own_timesheet','export_own_timesheet','delete_own_timesheet']
|
||||
TIMESHEET_OTHER: ['view_other_timesheet','start_other_timesheet','stop_other_timesheet','create_other_timesheet','edit_other_timesheet','export_other_timesheet','delete_other_timesheet']
|
||||
PROFILE: ['view_own_profile','edit_own_profile','password_own_profile','preferences_own_profile','api-token_own_profile']
|
||||
PROFILE_OTHER: ['view_other_profile','edit_other_profile','delete_other_profile','password_other_profile','roles_other_profile','preferences_other_profile','api-token_other_profile']
|
||||
TAGS: ['view_tag','delete_tag']
|
||||
USER: ['view_user','create_user','delete_user','role_permissions']
|
||||
RATE: ['view_rate_own_timesheet','edit_rate_own_timesheet']
|
||||
RATE_OTHER: ['view_rate_other_timesheet','edit_rate_other_timesheet']
|
||||
EXPORT: ['view_export','create_export','edit_export_own_timesheet','edit_export_other_timesheet']
|
||||
ROLE_USER: ['@TIMESHEET','@PROFILE']
|
||||
ROLE_TEAMLEAD: ['@INVOICE','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@EXPORT','@TAGS','@INVOICE_TEMPLATE','!delete_invoice_template','view_rate_own_timesheet','view_rate_other_timesheet','hourly-rate_own_profile']
|
||||
ROLE_ADMIN: ['@ACTIVITIES','@PROJECTS','@CUSTOMERS','@INVOICE','@INVOICE_TEMPLATE','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@RATE','@RATE_OTHER','@EXPORT','@TAGS','hourly-rate_own_profile','edit_exported_timesheet']
|
||||
ROLE_SUPER_ADMIN: ['@ACTIVITIES','@PROJECTS','@CUSTOMERS','@INVOICE','@INVOICE_TEMPLATE','@TIMESHEET','@TIMESHEET_OTHER','@PROFILE','@PROFILE_OTHER','@USER','@RATE','@RATE_OTHER','@EXPORT','@TAGS','hourly-rate_own_profile','hourly-rate_other_profile','delete_own_profile','roles_own_profile','system_information','system_configuration','plugins','edit_exported_timesheet']
|
||||
# mapping "sets" or permissions to user roles ("role name" = [array of "set names"])
|
||||
ROLE_USER: [TIMESHEET,PROFILE]
|
||||
ROLE_TEAMLEAD: [INVOICE,TIMESHEET,TIMESHEET_OTHER,PROFILE,EXPORT,TAGS]
|
||||
ROLE_ADMIN: [ACTIVITIES,PROJECTS,CUSTOMERS,INVOICE,INVOICE_TEMPLATE,TIMESHEET,TIMESHEET_OTHER,PROFILE,RATE,RATE_OTHER,EXPORT,TAGS]
|
||||
ROLE_SUPER_ADMIN: [ACTIVITIES,PROJECTS,CUSTOMERS,INVOICE,INVOICE_TEMPLATE,TIMESHEET,TIMESHEET_OTHER,PROFILE,PROFILE_OTHER,USER,RATE,RATE_OTHER,EXPORT,TAGS]
|
||||
maps:
|
||||
ROLE_USER: ['ROLE_USER']
|
||||
ROLE_TEAMLEAD: ['ROLE_TEAMLEAD']
|
||||
ROLE_ADMIN: ['ROLE_ADMIN']
|
||||
ROLE_SUPER_ADMIN: ['ROLE_SUPER_ADMIN']
|
||||
# add or remove single permissions
|
||||
roles:
|
||||
# adding single permissions to user roles, extending the definition from "sets" ("role name" = [array of "permissions"])
|
||||
ROLE_USER: []
|
||||
ROLE_TEAMLEAD: [view_invoice_template,create_invoice_template,edit_invoice_template,view_rate_own_timesheet,view_rate_other_timesheet,hourly-rate_own_profile]
|
||||
ROLE_ADMIN: [hourly-rate_own_profile,edit_exported_timesheet]
|
||||
ROLE_SUPER_ADMIN: [hourly-rate_own_profile,hourly-rate_other_profile,delete_own_profile,roles_own_profile,system_information,system_actions,system_configuration,plugins,edit_exported_timesheet]
|
||||
ROLE_TEAMLEAD: []
|
||||
ROLE_ADMIN: []
|
||||
ROLE_SUPER_ADMIN: []
|
||||
# --------------------------------------------------------------------------------
|
||||
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"build/app.js": "./app.js?376d50667e3784222761",
|
||||
"build/app.js": "./app.js?7f61ab551431df41b451",
|
||||
"build/app.css": "./app.css?954c34ace3717cfb9d9c80d2be5f8c68",
|
||||
"build/fonts/fa-solid-900.woff2": "./fonts/fa-solid-900.woff2?e8a92a29",
|
||||
"build/images/fa-solid-900.svg": "./images/fa-solid-900.svg?666a82cb",
|
||||
|
||||
@@ -11,10 +11,6 @@ namespace App\Controller;
|
||||
|
||||
use App\Constants;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Bundle\FrameworkBundle\Console\Application;
|
||||
use Symfony\Component\Console\Input\ArrayInput;
|
||||
use Symfony\Component\Console\Output\BufferedOutput;
|
||||
use Symfony\Component\HttpKernel\KernelInterface;
|
||||
use Symfony\Component\Routing\Annotation\Route;
|
||||
|
||||
/**
|
||||
@@ -43,11 +39,6 @@ class AboutController extends AbstractController
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function debugAction()
|
||||
{
|
||||
return $this->getAboutView();
|
||||
}
|
||||
|
||||
protected function getAboutView(array $additional = [])
|
||||
{
|
||||
$phpInfo = $this->getPhpInfo();
|
||||
unset($phpInfo[0]);
|
||||
@@ -89,8 +80,7 @@ class AboutController extends AbstractController
|
||||
],
|
||||
'info' => $phpInfo,
|
||||
'settings' => $settings,
|
||||
],
|
||||
$additional
|
||||
]
|
||||
));
|
||||
}
|
||||
|
||||
@@ -119,28 +109,6 @@ class AboutController extends AbstractController
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/flush-cache", name="system_flush_cache", methods={"GET"})
|
||||
*
|
||||
* @Security("is_granted('system_actions')")
|
||||
*/
|
||||
public function rebuildContainer(KernelInterface $kernel)
|
||||
{
|
||||
$application = new Application($kernel);
|
||||
$application->setAutoExit(false);
|
||||
|
||||
$input = new ArrayInput([
|
||||
'command' => 'cache:clear',
|
||||
'--env' => $kernel->getEnvironment(),
|
||||
'-n',
|
||||
]);
|
||||
|
||||
$output = new BufferedOutput();
|
||||
$application->run($input, $output);
|
||||
|
||||
return $this->getAboutView(['content_action' => $output->fetch()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @author https://php.net/manual/en/function.phpinfo.php#117961
|
||||
* @return array
|
||||
|
||||
@@ -14,6 +14,7 @@ use App\Entity\User;
|
||||
use App\Form\Toolbar\UserToolbarForm;
|
||||
use App\Form\UserCreateType;
|
||||
use App\Repository\Query\UserQuery;
|
||||
use App\Security\RolePermissionManager;
|
||||
use Pagerfanta\Pagerfanta;
|
||||
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Security;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
@@ -173,6 +174,22 @@ class UserController extends AbstractController
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Route(path="/permissions", name="admin_user_permissions", methods={"GET", "POST"})
|
||||
* @Security("is_granted('role_permissions')")
|
||||
*
|
||||
* @param RolePermissionManager $manager
|
||||
* @return \Symfony\Component\HttpFoundation\Response
|
||||
*/
|
||||
public function permissions(RolePermissionManager $manager)
|
||||
{
|
||||
return $this->render('user/permissions.html.twig', [
|
||||
'roles' => $manager->getRoles(),
|
||||
'permissions' => $manager->getPermissions(),
|
||||
'manager' => $manager,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param UserQuery $query
|
||||
* @return \Symfony\Component\Form\FormInterface
|
||||
|
||||
@@ -71,14 +71,8 @@ class AppExtension extends Extension
|
||||
*/
|
||||
protected function createPermissionParameter(array $config, ContainerBuilder $container)
|
||||
{
|
||||
$roles = [];
|
||||
foreach ($config['maps'] as $role => $sets) {
|
||||
if (!isset($config['roles'][$role])) {
|
||||
$exception = new InvalidConfigurationException(
|
||||
'Configured permission set includes unknown role "' . $role . '"'
|
||||
);
|
||||
$exception->setPath('kimai.permissions.maps.' . $role);
|
||||
throw $exception;
|
||||
}
|
||||
foreach ($sets as $set) {
|
||||
if (!isset($config['sets'][$set])) {
|
||||
$exception = new InvalidConfigurationException(
|
||||
@@ -87,12 +81,59 @@ class AppExtension extends Extension
|
||||
$exception->setPath('kimai.permissions.maps.' . $role);
|
||||
throw $exception;
|
||||
}
|
||||
$config['roles'][$role] = array_unique(array_merge($config['roles'][$role], $config['sets'][$set]));
|
||||
$roles[$role] = array_merge($roles[$role] ?? [], $this->getFilteredPermissions(
|
||||
$this->extractSinglePermissionsFromSet($config, $set)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// delete forbidden permissions from roles
|
||||
foreach (array_keys($config['maps']) as $name) {
|
||||
$config['roles'][$name] = $this->getFilteredPermissions(
|
||||
array_unique(array_merge($roles[$name], $config['roles'][$name] ?? []))
|
||||
);
|
||||
}
|
||||
|
||||
$container->setParameter('kimai.permissions', $config['roles']);
|
||||
}
|
||||
|
||||
protected function getFilteredPermissions(array $permissions): array
|
||||
{
|
||||
$deleteFromArray = array_filter($permissions, function ($permission) {
|
||||
return $permission[0] == '!';
|
||||
});
|
||||
|
||||
return array_filter($permissions, function ($permission) use ($deleteFromArray) {
|
||||
if ($permission[0] == '!') {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !in_array('!' . $permission, $deleteFromArray);
|
||||
});
|
||||
}
|
||||
|
||||
protected function extractSinglePermissionsFromSet(array $permissions, string $name): array
|
||||
{
|
||||
if (!isset($permissions['sets'][$name])) {
|
||||
throw new InvalidConfigurationException('Unknown permission set "' . $name . '"');
|
||||
}
|
||||
|
||||
$result = [];
|
||||
|
||||
foreach ($permissions['sets'][$name] as $permissionName) {
|
||||
if ($permissionName[0] == '@') {
|
||||
$result = array_merge(
|
||||
$result,
|
||||
$this->extractSinglePermissionsFromSet($permissions, substr($permissionName, 1))
|
||||
);
|
||||
} else {
|
||||
$result[] = $permissionName;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $config
|
||||
* @param ContainerBuilder $container
|
||||
|
||||
@@ -434,7 +434,6 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayNode('sets')
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->performNoDeepMerging()
|
||||
->arrayPrototype()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
@@ -445,7 +444,6 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayNode('maps')
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->performNoDeepMerging()
|
||||
->arrayPrototype()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
@@ -456,13 +454,18 @@ class Configuration implements ConfigurationInterface
|
||||
->arrayNode('roles')
|
||||
->requiresAtLeastOneElement()
|
||||
->useAttributeAsKey('key')
|
||||
->performNoDeepMerging()
|
||||
->arrayPrototype()
|
||||
->useAttributeAsKey('key')
|
||||
->isRequired()
|
||||
->prototype('scalar')->end()
|
||||
->defaultValue([])
|
||||
->end()
|
||||
->defaultValue([
|
||||
'ROLE_USER' => [],
|
||||
'ROLE_TEAMLEAD' => [],
|
||||
'ROLE_ADMIN' => [],
|
||||
'ROLE_SUPER_ADMIN' => [],
|
||||
])
|
||||
->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
@@ -55,7 +56,7 @@ class UserCreateType extends UserEditType
|
||||
public function __configureOptions(OptionsResolver $resolver)
|
||||
{
|
||||
$resolver->setDefaults([
|
||||
'class' => 'Kimai:User',
|
||||
'class' => User::class,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,13 +16,10 @@ class RolePermissionManager
|
||||
*/
|
||||
protected $permissions = [];
|
||||
/**
|
||||
* @var array
|
||||
* @var string[]
|
||||
*/
|
||||
protected $knownPermissions = [];
|
||||
|
||||
/**
|
||||
* @param array $permissions
|
||||
*/
|
||||
public function __construct(array $permissions)
|
||||
{
|
||||
$this->permissions = $permissions;
|
||||
@@ -33,30 +30,12 @@ class RolePermissionManager
|
||||
$this->knownPermissions = array_unique($this->knownPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function isRegisteredPermission($permission)
|
||||
public function isRegisteredPermission(string $permission): bool
|
||||
{
|
||||
return in_array($permission, $this->knownPermissions);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
* @return bool
|
||||
*/
|
||||
public function roleHasPermission($role)
|
||||
{
|
||||
return isset($this->permissions[$role]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $role
|
||||
* @param string $permission
|
||||
* @return bool
|
||||
*/
|
||||
public function hasPermission($role, $permission)
|
||||
public function hasPermission(string $role, string $permission): bool
|
||||
{
|
||||
if (!isset($this->permissions[$role])) {
|
||||
return false;
|
||||
@@ -64,4 +43,19 @@ class RolePermissionManager
|
||||
|
||||
return in_array($permission, $this->permissions[$role]);
|
||||
}
|
||||
|
||||
public function getRoles(): array
|
||||
{
|
||||
return array_keys($this->permissions);
|
||||
}
|
||||
|
||||
public function roleHasPermission(string $role): bool
|
||||
{
|
||||
return isset($this->permissions[$role]);
|
||||
}
|
||||
|
||||
public function getPermissions(): array
|
||||
{
|
||||
return $this->knownPermissions;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +94,8 @@ class Extensions extends AbstractExtension
|
||||
'profile-stats' => 'far fa-chart-bar',
|
||||
'profile' => 'fas fa-user-edit',
|
||||
'warning' => 'fas fa-exclamation-triangle',
|
||||
'permissions' => 'fas fa-user-lock',
|
||||
'back' => 'fas fa-long-arrow-alt-left',
|
||||
];
|
||||
|
||||
/**
|
||||
|
||||
@@ -4,22 +4,6 @@
|
||||
{% block page_subtitle %}{{ 'about.subtitle'|trans({}, 'about') }}{% endblock %}
|
||||
|
||||
{% block about_box %}
|
||||
{% if is_granted('system_actions') %}
|
||||
{% embed '@AdminLTE/Widgets/box-widget.html.twig' with {'boxtype': 'warning'} %}
|
||||
{% block box_title %}{{ 'label.actions'|trans }}{% endblock %}
|
||||
{% block box_body %}
|
||||
{% if content_action is defined and content_action is not empty %}
|
||||
<pre>
|
||||
{{ content_action }}
|
||||
</pre>
|
||||
{% endif %}
|
||||
|
||||
<p>
|
||||
<a href="{{ path('system_flush_cache') }}" class="btn btn-primary" onclick="$(this).html('Clear cache <i class=\'fas fa-spinner fa-spin\'></i>')">Clear cache</a>
|
||||
</p>
|
||||
{% endblock %}
|
||||
{% endembed %}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block about %}
|
||||
|
||||
@@ -131,6 +131,9 @@
|
||||
}
|
||||
|
||||
ALERT.question(message, function(value) {
|
||||
if (!value) {
|
||||
return;
|
||||
}
|
||||
var btn = $(button);
|
||||
var exportButtons = $('.exportBtn');
|
||||
// disabling does not yet work...
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'date': 'alwaysVisible',
|
||||
@@ -16,16 +17,7 @@
|
||||
|
||||
{% block page_title %}{{ 'invoice.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'invoice.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}
|
||||
{% set actions = {'filter': '#collapseInvoice', 'visibility': '#modal_invoice'} %}
|
||||
{% if is_granted('create_invoice_template') %}
|
||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_invoice_template') %}
|
||||
{% set actions = actions|merge({'list': path('admin_invoice_template')}) %}
|
||||
{% endif %}
|
||||
{{ widgets.page_actions(actions) }}
|
||||
{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoices('index') }}{% endblock %}
|
||||
|
||||
{% block main_before %}
|
||||
{{ toolbar.toolbar(form, 'collapseInvoice', true) }}
|
||||
|
||||
@@ -1,21 +1,11 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
|
||||
{% block page_title %}{{ 'admin_invoice_template.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_invoice_template.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}
|
||||
{# TODO move me to actions macro #}
|
||||
{% set actions = {} %}
|
||||
{% if is_granted('create_invoice_template') %}
|
||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_invoice') %}
|
||||
{% set actions = actions|merge({'invoice': path('invoice')}) %}
|
||||
{% endif %}
|
||||
{% set event = trigger('actions.invoice_templates', {'actions': actions, 'view': 'index'}) %}
|
||||
{{ widgets.page_actions(actions) }}
|
||||
{% endblock %}
|
||||
{% block page_actions %}{{ actions.invoice_templates('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
{% if entries.count == 0 %}
|
||||
|
||||
@@ -48,16 +48,43 @@
|
||||
|
||||
{% macro users(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {'filter': '#collapseUserAdmin', 'visibility': '#modal_user_admin'} %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% if view == 'index' %}
|
||||
{% set actions = actions|merge({'filter': '#collapseUserAdmin', 'visibility': '#modal_user_admin'}) %}
|
||||
{% else %}
|
||||
{% set actions = actions|merge({'back': path('admin_user')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view != 'permissions' and is_granted('role_permissions') %}
|
||||
{% set actions = actions|merge({'permissions': path('admin_user_permissions')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('create_user') %}
|
||||
{% set actions = actions|merge({'create': path('admin_user_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if view == 'index' %}
|
||||
{% set actions = actions|merge({'help': {'url': 'users.html'|docu_link, 'target': '_blank'}}) %}
|
||||
{% elseif view == 'permissions' %}
|
||||
{% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set event = trigger('actions.users', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user_permissions(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
|
||||
{% set actions = actions|merge({'back': path('admin_user')}) %}
|
||||
{% set actions = actions|merge({'help': {'url': 'permissions.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.user_permissions', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro user(user, view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% set actions = {} %}
|
||||
@@ -371,3 +398,39 @@
|
||||
{% set event = trigger('actions.tag', {'actions': actions, 'view': view, 'tag': tag}) %}
|
||||
{{ widgets.table_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoices(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {'filter': '#collapseInvoice', 'visibility': '#modal_invoice'} %}
|
||||
{% if is_granted('create_invoice_template') %}
|
||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
||||
{% endif %}
|
||||
{% if is_granted('view_invoice_template') %}
|
||||
{% set actions = actions|merge({'list': path('admin_invoice_template')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.invoices', {'actions': actions, 'view': view}) %}
|
||||
{{ widgets.page_actions(event.payload.actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro invoice_templates(view) %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
|
||||
{% set actions = {} %}
|
||||
{% if is_granted('view_invoice') %}
|
||||
{% set actions = actions|merge({'back': path('invoice')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% if is_granted('create_invoice_template') %}
|
||||
{% set actions = actions|merge({'create': path('admin_invoice_template_create')}) %}
|
||||
{% endif %}
|
||||
|
||||
{% set actions = actions|merge({'help': {'url': 'invoices.html'|docu_link, 'target': '_blank'}}) %}
|
||||
|
||||
{% set event = trigger('actions.invoice_templates', {'actions': actions, 'view': 'index'}) %}
|
||||
{{ widgets.page_actions(actions) }}
|
||||
{% endmacro %}
|
||||
|
||||
|
||||
@@ -70,9 +70,18 @@
|
||||
{% endspaceless %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_header(name, columns, skipStripped, reloadEvent) %}
|
||||
{% import _self as macro %}
|
||||
<div class="box box-{{ admin_lte_context.widget.type }} data_table" id="datatable_{{ name }}">
|
||||
{% macro data_table_header_options(name, columns, options) %}
|
||||
{% set skipStripped = options.skipStripped|default(false) %}
|
||||
{% set reloadEvent = options.reloadEvent|default('') %}
|
||||
{% set translationDomain = options.translationDomain|default('messages') %}
|
||||
{# |default does not work here, as the prefix might be an empty string #}
|
||||
{% set translationPrefix = 'label.' %}
|
||||
{% if options.translationPrefix is defined %}
|
||||
{% set translationPrefix = options.translationPrefix %}
|
||||
{% endif %}
|
||||
|
||||
{% import _self as macro %}
|
||||
<div class="box box-{{ admin_lte_context.widget.type }} data_table" id="datatable_{{ name }}">
|
||||
<div class="box-body no-padding">
|
||||
<div class="dataTables_wrapper form-inline dt-bootstrap">
|
||||
<div class="row">
|
||||
@@ -81,13 +90,18 @@
|
||||
<thead>
|
||||
<tr>
|
||||
{%- for title, class in columns -%}
|
||||
<th data-field="{{ title }}" class="{{ macro.data_table_column_class(name, columns, title) }}">{% if title is not empty and title != 'actions' %}{{ ('label.' ~ title)|trans }}{% endif %}</th>
|
||||
<th data-field="{{ title }}" class="{{ macro.data_table_column_class(name, columns, title) }}">{% if title is not empty and title != 'actions' %}{{ (translationPrefix ~ title)|trans({}, translationDomain) }}{% endif %}</th>
|
||||
{%- endfor -%}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_header(name, columns, skipStripped, reloadEvent) %}
|
||||
{% import _self as macro %}
|
||||
{{ macro.data_table_header_options(name, columns, {'skipStripped': skipStripped, 'reloadEvent': reloadEvent}) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro data_table_footer(entries, route) %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<h2 class="page-header">{{ title|trans }}</h2>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro label_visible(visible) %}
|
||||
{% macro label_boolean(visible) %}
|
||||
{% import _self as macro %}
|
||||
{% if visible %}
|
||||
{{ macro.label('yes', 'success') }}
|
||||
@@ -33,6 +33,11 @@
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro label_visible(visible) %}
|
||||
{% import _self as macro %}
|
||||
{{ macro.label_boolean(visible) }}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro label_role(role) %}
|
||||
{% import _self as macro %}
|
||||
{% if role == 'ROLE_SUPER_ADMIN' %}
|
||||
|
||||
40
templates/user/permissions.html.twig
Normal file
40
templates/user/permissions.html.twig
Normal file
@@ -0,0 +1,40 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
{% import "macros/datatables.html.twig" as tables %}
|
||||
{% import "macros/widgets.html.twig" as widgets %}
|
||||
{% import "macros/toolbar.html.twig" as toolbar %}
|
||||
{% import "macros/actions.html.twig" as actions %}
|
||||
|
||||
{% set columns = {
|
||||
'label.name': 'alwaysVisible',
|
||||
} %}
|
||||
|
||||
{% for role in roles %}
|
||||
{% set columns = columns|merge({
|
||||
(role): 'alwaysVisible text-center',
|
||||
}) %}
|
||||
{% endfor %}
|
||||
|
||||
{% set tableName = 'user_admin_permissions' %}
|
||||
|
||||
{% block page_title %}{{ 'admin_user.title'|trans }}{% endblock %}
|
||||
{% block page_subtitle %}{{ 'admin_user.subtitle'|trans }}{% endblock %}
|
||||
{% block page_actions %}{{ actions.user_permissions('index') }}{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
||||
{{ tables.data_table_header_options(tableName, columns, {'translationPrefix': ''}) }}
|
||||
|
||||
{% for permission in permissions|sort %}
|
||||
<tr>
|
||||
<td>{{ permission }}</td>
|
||||
{% for role in roles %}
|
||||
<td class="text-center">
|
||||
{{ widgets.label_boolean(manager.permission(role, permission)) }}
|
||||
</td>
|
||||
{% endfor %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
||||
{{ tables.data_table_footer(permissions) }}
|
||||
|
||||
{% endblock %}
|
||||
@@ -45,9 +45,7 @@ class AboutControllerTest extends ControllerBaseTest
|
||||
$this->assertContains('<h3 class="box-title">PHP</h3>', $content);
|
||||
$this->assertContains('<h3 class="box-title">Server</h3>', $content);
|
||||
|
||||
$this->assertContains('Actions', $content);
|
||||
$this->assertContains('', $content);
|
||||
$this->assertContains('PHP', $content);
|
||||
$this->assertContains('<a href="/en/about/flush-cache"', $content);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ class UserControllerTest extends ControllerBaseTest
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/user/');
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_user_admin', 5);
|
||||
}
|
||||
|
||||
public function testCreateAction()
|
||||
@@ -196,4 +197,18 @@ class UserControllerTest extends ControllerBaseTest
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testPermissionsIsSecure()
|
||||
{
|
||||
$this->assertUrlIsSecured('/admin/user/permissions');
|
||||
$this->assertUrlIsSecuredForRole(User::ROLE_ADMIN, '/admin/user/permissions');
|
||||
}
|
||||
|
||||
public function testPermissions()
|
||||
{
|
||||
$client = $this->getClientForAuthenticatedUser(User::ROLE_SUPER_ADMIN);
|
||||
$this->assertAccessIsGranted($client, '/admin/user/permissions');
|
||||
$this->assertHasDataTable($client);
|
||||
$this->assertDataTableRowCount($client, 'datatable_user_admin_permissions', 66);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ abstract class AbstractVoterTest extends TestCase
|
||||
$roleUser = [];
|
||||
$roleTeamlead = ['view_rate_own_timesheet', 'view_rate_other_timesheet', 'hourly-rate_own_profile'];
|
||||
$roleAdmin = ['hourly-rate_own_profile', 'edit_exported_timesheet'];
|
||||
$roleSuperAdmin = ['hourly-rate_own_profile', 'hourly-rate_other_profile', 'delete_own_profile', 'roles_own_profile', 'system_information', 'system_actions', 'system_configuration', 'plugins', 'edit_exported_timesheet'];
|
||||
$roleSuperAdmin = ['hourly-rate_own_profile', 'hourly-rate_other_profile', 'delete_own_profile', 'roles_own_profile', 'system_information', 'system_configuration', 'plugins', 'edit_exported_timesheet'];
|
||||
|
||||
$permissions = [
|
||||
'ROLE_USER' => array_merge($timesheet, $profile, $roleUser),
|
||||
|
||||
Reference in New Issue
Block a user