Skip to content

Instantly share code, notes, and snippets.

@emulienfou
Last active August 21, 2018 17:43
Show Gist options
  • Save emulienfou/002317d8015fea34ef7fd0c49b8e59ac to your computer and use it in GitHub Desktop.
Save emulienfou/002317d8015fea34ef7fd0c49b8e59ac to your computer and use it in GitHub Desktop.
EasyAdmin role based permissions
{% for action in actions %}
{# Check role based permission for current user #}
{% if action.roles is not defined or in_array(action.roles, app.user.roles) %}
{% if 'list' == action.name %}
{% set action_href = request_parameters.referer|default('') ? request_parameters.referer|easyadmin_urldecode : path('easyadmin', request_parameters|merge({ action: 'list' })) %}
{% elseif 'method' == action.type %}
{% set action_href = path('easyadmin', request_parameters|merge({ action: action.name, id: item_id })) %}
{% elseif 'route' == action.type %}
{% set action_href = path(action.name, request_parameters|merge({ action: action.name, id: item_id })) %}
{% endif %}
<a class="{{ action.css_class|default('') }}" title="{{ action.title|default('') is empty ? '' : action.title|trans(trans_parameters, translation_domain) }}" href="{{ action_href }}" target="{{ action.target }}">
{%- if action.icon %}<i class="fa fa-{{ action.icon }}"></i> {% endif -%}
{%- if action.label is defined and not action.label is empty -%}
{{ action.label|trans(arguments = trans_parameters|merge({ '%entity_id%': item_id }), domain = translation_domain) }}
{%- endif -%}
</a>
{% endif %}
{% endfor %}
<?php
namespace Acme\Controller;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AdminController as BaseAdminController;
use EasyCorp\Bundle\EasyAdminBundle\Exception\ForbiddenActionException;
use Symfony\Component\HttpFoundation\{RedirectResponse, Request, Response};
use Symfony\Component\Routing\Annotation\Route;
/**
* Class AdminController
*
* @package Acme\Controller
*/
class AdminController extends BaseAdminController
{
/**
* @Route("/", name="easyadmin")
* @Route("/", name="admin")
* The 'admin' route is deprecated since version 1.8.0 and it will be removed in 2.0.
*
* @param Request $request
*
* @return RedirectResponse|Response
*/
public function indexAction(Request $request)
{
if ('admin' === $request->attributes->get('_route')) {
trigger_error(sprintf('The "admin" route is deprecated since version 1.8.0 and it will be removed in 2.0. Use the "easyadmin" route instead.'),
E_USER_DEPRECATED);
}
$this->initialize($request);
if (null === $request->query->get('entity')) {
if (in_array('ROLE_TEACHER', $this->getUser()->getRoles())) {
return $this->redirect($this->get('router')->generate('easyadmin', [
'action' => 'list',
'entity' => 'Availability'
]));
}
return $this->redirectToBackendHomepage();
}
$action = $request->query->get('action', 'list');
if (!$this->isActionAllowed($action)) {
throw new ForbiddenActionException(['action' => $action, 'entity_name' => $this->entity['name']]);
}
$this->checkPermissions();
return $this->executeDynamicMethod($action . '<EntityName>Action');
}
/**
* Check roles based permissions
*
* @see: https://github.com/EasyCorp/EasyAdminBundle/issues/807
*/
protected function checkPermissions()
{
if (!$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY')) {
throw $this->createAccessDeniedException();
}
$easyAdmin = $this->request->attributes->get('easyadmin');
$action = $this->request->query->get('action');
$perms = [];
// Roles by entity
if (isset($easyAdmin['entity']['roles'])) {
$perms = $easyAdmin['entity']['roles'];
}
// Roles by entity's action
if (isset($easyAdmin['entity'][$action]['roles'])) {
$perms = $easyAdmin['entity'][$action]['roles'];
}
$this->denyAccessUnlessGranted($perms);
}
}
easy_admin:
design:
menu:
- { label: 'Home', route: 'index', icon: 'home' }
- { entity: 'User', label: 'Users', icon: 'users', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }
- { entity: 'Student', label: 'Student', icon: 'graduation-cap', roles: 'ROLE_TEACHER' }
entities:
User:
class: Acme\Entity\User
roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN']
Student:
class: Acme\Entity\Student
roles: ['ROLE_TEACHER', 'ROLE_ADMIN', 'ROLE_SUPER_ADMIN']
actions:
- 'edit'
- { name: 'delete', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }
fields:
- { property: 'student', label: 'Student' }
- { property: 'user', label: 'User', roles: ['ROLE_ADMIN', 'ROLE_SUPER_ADMIN'] }
{% set _entity_config = easyadmin_entity(app.request.query.get('entity')) %}
{% trans_default_domain _entity_config.translation_domain %}
{% set _trans_parameters = { '%entity_name%': _entity_config.name|trans, '%entity_label%': _entity_config.label|trans } %}
{% extends _entity_config.templates.layout %}
{% set _request_parameters = app.request.query.all|merge(_request_parameters|default({}))|merge({
action: app.request.get('action'),
entity: _entity_config.name,
menuIndex: app.request.get('menuIndex'),
submenuIndex: app.request.get('submenuIndex'),
sortField: app.request.get('sortField', ''),
sortDirection: app.request.get('sortDirection', 'DESC'),
page: app.request.get('page', 1),
referer: null
}) %}
{% if 'search' == app.request.get('action') %}
{% set _request_parameters = _request_parameters|merge({
query: app.request.get('query')|default(''),
sortField: _entity_config.search.sort.field|default(app.request.get('sortField', '')),
sortDirection: _entity_config.search.sort.direction|default(app.request.get('sortDirection', 'DESC')),
}) %}
{% endif %}
{% set _request_parameters = _request_parameters|merge({ referer: path('easyadmin', _request_parameters)|url_encode }) %}
{% block body_id 'easyadmin-list-' ~ _entity_config.name %}
{% block body_class 'list list-' ~ _entity_config.name|lower %}
{% block content_title %}
{% spaceless %}
{% if 'search' == app.request.get('action') %}
{% set _default_title = 'search.page_title'|transchoice(paginator.nbResults, {}, 'EasyAdminBundle') %}
{{ (_entity_config.search.title is defined ? _entity_config.search.title|transchoice(paginator.nbResults) : _default_title)|raw }}
{% else %}
{% set _default_title = 'list.page_title'|trans(_trans_parameters, 'EasyAdminBundle') %}
{{ (_entity_config.list.title is defined ? _entity_config.list.title|trans(_trans_parameters) : _default_title)|raw }}
{% endif %}
{% endspaceless %}
{% endblock %}
{% block content_header %}
<div class="row">
<div class="col-sm-5">
{% block content_title_wrapper %}
<h1 class="title">{{ block('content_title') }}</h1>
{% endblock %}
</div>
<div class="col-sm-7">
<div class="global-actions">
{% block global_actions %}
{% if easyadmin_action_is_enabled_for_list_view('search', _entity_config.name) %}
{% set _action = easyadmin_get_action_for_list_view('search', _entity_config.name) %}
{% block search_action %}
<div class="form-action {{ _action.css_class|default('') }}">
<form method="get" action="{{ path('easyadmin') }}">
{% block search_form %}
<input type="hidden" name="action" value="search">
<input type="hidden" name="entity" value="{{ _request_parameters.entity }}">
<input type="hidden" name="sortField" value="{{ _entity_config.search.sort.field|default(_request_parameters.sortField) }}">
<input type="hidden" name="sortDirection" value="{{ _entity_config.search.sort.direction|default(_request_parameters.sortDirection) }}">
<input type="hidden" name="menuIndex" value="{{ _request_parameters.menuIndex }}">
<input type="hidden" name="submenuIndex" value="{{ _request_parameters.submenuIndex }}">
<div class="input-group">
<input class="form-control" type="search" name="query" value="{{ app.request.get('query')|default('') }}">
<span class="input-group-btn">
<button class="btn" type="submit" formtarget="{{ _action.target }}">
<i class="fa fa-search"></i>
<span class="hidden-xs hidden-sm">{{ _action.label|default('action.search')|trans(_trans_parameters) }}</span>
</button>
</span>
</div>
{% endblock %}
</form>
</div>
{% endblock search_action %}
{% endif %}
{% if easyadmin_action_is_enabled_for_list_view('new', _entity_config.name) %}
{% set _action = easyadmin_get_action_for_list_view('new', _entity_config.name) %}
{% block new_action %}
<div class="button-action">
<a class="{{ _action.css_class|default('') }}" href="{{ path('easyadmin', _request_parameters|merge({ action: _action.name })) }}" target="{{ _action.target }}">
{% if _action.icon %}<i class="fa fa-{{ _action.icon }}"></i>{% endif %}
{{ _action.label is defined and not _action.label is empty ? _action.label|trans(_trans_parameters) }}
</a>
</div>
{% endblock new_action %}
{% endif %}
{% endblock global_actions %}
</div>
</div>
</div>
{% endblock content_header %}
{% block main %}
{% set _list_item_actions = easyadmin_get_actions_for_list_item(_entity_config.name) %}
<div class="table-responsive">
<table class="table">
<thead>
{% block table_head %}
<tr>
{% for field, metadata in fields %}
{% set isSortingField = metadata.property == app.request.get('sortField')|split('.')|first %}
{% set nextSortDirection = isSortingField ? (app.request.get('sortDirection') == 'DESC' ? 'ASC' : 'DESC') : 'DESC' %}
{% set _column_label = (metadata.label ?: field|humanize)|trans(_trans_parameters) %}
{% set _column_icon = isSortingField ? (nextSortDirection == 'DESC' ? 'fa-caret-up' : 'fa-caret-down') : 'fa-sort' %}
{# Check role based permission for current user #}
{% if metadata.roles is not defined or in_array(metadata.roles, app.user.roles) %}
<th data-property-name="{{ metadata.property }}"
class="{{ isSortingField ? 'sorted' }} {{ metadata.virtual ? 'virtual' }} {{ metadata.dataType|lower }} {{ metadata.css_class }}" {{ easyadmin_config('design.rtl') ? 'dir="rtl"' }}>
{% if metadata.sortable %}
<a href="{{ path('easyadmin', _request_parameters|merge({ page: 1, sortField: metadata.property, sortDirection: nextSortDirection })) }}">
<i class="fa {{ _column_icon }}"></i>
{{ _column_label|raw }}
</a>
{% else %}
<span>{{ _column_label|raw }}</span>
{% endif %}
</th>
{% endif %}
{% endfor %}
{% if _list_item_actions|length > 0 %}
<th {{ easyadmin_config('design.rtl') ? 'dir="rtl"' }}>
<span>{{ 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') }}</span>
</th>
{% endif %}
</tr>
{% endblock table_head %}
</thead>
<tbody>
{% block table_body %}
{% for item in paginator.currentPageResults %}
{# the empty string concatenation is needed when the primary key is an object (e.g. an Uuid object) #}
{% set _item_id = '' ~ attribute(item, _entity_config.primary_key_field_name) %}
<tr data-id="{{ _item_id }}">
{% for field, metadata in fields %}
{% set isSortingField = metadata.property == app.request.get('sortField') %}
{% set _column_label = (metadata.label ?: field|humanize)|trans(_trans_parameters) %}
{# Check role based permission for current user #}
{% if metadata.roles is not defined or in_array(metadata.roles, app.user.roles) %}
<td data-label="{{ _column_label }}"
class="{{ isSortingField ? 'sorted' }} {{ metadata.dataType|lower }} {{ metadata.css_class }}" {{ easyadmin_config('design.rtl') ? 'dir="rtl"' }}>
{{ easyadmin_render_field_for_list_view(_entity_config.name, item, metadata) }}
</td>
{% endif %}
{% endfor %}
{% if _list_item_actions|length > 0 %}
{% set _column_label = 'list.row_actions'|trans(_trans_parameters, 'EasyAdminBundle') %}
<td data-label="{{ _column_label }}" class="actions">
{% block item_actions %}
{{ include('@EasyAdmin/default/includes/_actions.html.twig', {
actions: _list_item_actions,
request_parameters: _request_parameters,
translation_domain: _entity_config.translation_domain,
trans_parameters: _trans_parameters,
item_id: _item_id
}, with_context = false) }}
{% endblock item_actions %}
</td>
{% endif %}
</tr>
{% else %}
<tr>
<td class="no-results" colspan="{{ _list_item_actions|length > 0 ? fields|length + 1 : fields|length }}">
{{ 'search.no_results'|trans(_trans_parameters, 'EasyAdminBundle') }}
</td>
</tr>
{% endfor %}
{% endblock table_body %}
</tbody>
</table>
</div>
{% block paginator %}
{{ include(_entity_config.templates.paginator) }}
{% endblock paginator %}
{% block delete_form %}
{% set referer = paginator.currentPage == paginator.nbPages and 1 != paginator.currentPage and 1 == paginator.currentPageResults|length
? path('easyadmin', app.request.query|merge({ page: app.request.query.get('page') - 1 }))
: app.request.requestUri
%}
{{ include('@EasyAdmin/default/includes/_delete_form.html.twig', {
view: 'list',
referer: referer|url_encode,
delete_form: delete_form_template,
_translation_domain: _entity_config.translation_domain,
_trans_parameters: _trans_parameters,
_entity_config: _entity_config,
}, with_context = false) }}
{% endblock delete_form %}
{% endblock main %}
{% block body_javascript %}
{{ parent() }}
<script type="text/javascript">
$(function() {
$('#main').find('table .toggle input[type="checkbox"]').change(function() {
var toggle = $(this);
var newValue = toggle.prop('checked');
var oldValue = !newValue;
var columnIndex = $(this).closest('td').index() + 1;
var propertyName = $('table th.toggle:nth-child(' + columnIndex + ')').data('property-name');
var toggleUrl = "{{ path('easyadmin', { action: 'edit', entity: _entity_config.name, view: 'list' })|raw }}"
+ "&id=" + $(this).closest('tr').data('id')
+ "&property=" + propertyName
+ "&newValue=" + newValue.toString();
var toggleRequest = $.ajax({ type: "GET", url: toggleUrl, data: {} });
toggleRequest.done(function(result) {});
toggleRequest.fail(function() {
// in case of error, restore the original value and disable the toggle
toggle.bootstrapToggle(oldValue == true ? 'on' : 'off');
toggle.bootstrapToggle('disable');
});
});
$('.action-delete').on('click', function(e) {
e.preventDefault();
var id = $(this).parents('tr').first().data('id');
$('#modal-delete').modal({ backdrop: true, keyboard: true })
.off('click', '#modal-delete-button')
.on('click', '#modal-delete-button', function () {
var deleteForm = $('#delete-form');
deleteForm.attr('action', deleteForm.attr('action').replace('__id__', id));
deleteForm.trigger('submit');
});
});
});
</script>
{% if 'search' == app.request.get('action') %}
<script type="text/javascript">
var _search_query = "{{ app.request.get('query')|default('')|e('js') }}";
// the original query is prepended to allow matching exact phrases in addition to single words
$('#main').find('table tbody').highlight($.merge([_search_query], _search_query.split(' ')));
</script>
{% endif %}
{% endblock %}
{% macro render_menu_item(item, translation_domain) %}
{% if item.type == 'divider' %}
{{ item.label|trans(domain = translation_domain) }}
{% else %}
{% set menu_params = { menuIndex: item.menu_index, submenuIndex: item.submenu_index } %}
{% set path =
item.type == 'link' ? item.url :
item.type == 'route' ? path(item.route, item.params) :
item.type == 'entity' ? path('easyadmin', { entity: item.entity, action: 'list' }|merge(menu_params)|merge(item.params)) :
item.type == 'empty' ? '#' : ''
%}
{# if the URL generated for the route belongs to the backend, regenerate
the URL to include the menu_params to display the selected menu item
(this is checked comparing the beginning of the route URL with the backend homepage URL)
#}
{% if item.type == 'route' and (path starts with path('easyadmin')) %}
{% set path = path(item.route, menu_params|merge(item.params)) %}
{% endif %}
<a href="{{ path }}" {% if item.target|default(false) %}target="{{ item.target }}"{% endif %}>
{% if item.icon is not empty %}<i class="fa {{ item.icon }}"></i>{% endif %}
<span>{{ item.label|trans(domain = translation_domain) }}</span>
{% if item.children|default([]) is not empty %}<span class="pull-right-container"><i class="fa fa-angle-left pull-right"></i></span>{% endif %}
</a>
{% endif %}
{% endmacro %}
{% import _self as helper %}
{% block main_menu_before %}{% endblock %}
<ul class="sidebar-menu" data-widget="tree" data-animation-speed="250">
{% set _menu_items = easyadmin_config('design.menu') %}
{% block main_menu %}
{% for item in _menu_items %}
{# Check role based permission for current user #}
{% if item.roles is not defined or in_array(item.roles, app.user.roles) %}
{% block menu_item %}
<li class="{{ item.type == 'divider' ? 'header' }} {{ item.children is not empty ? 'treeview' }} {{ app.request.query.get('menuIndex')|default(-1) == loop.index0 ? 'active' }} {{ app.request.query.get('submenuIndex')|default(-1) != -1 ? 'submenu-active' }}">
{{ helper.render_menu_item(item, _entity_config.translation_domain|default('messages')) }}
{% if item.children|default([]) is not empty %}
<ul class="treeview-menu">
{% for subitem in item.children %}
{% block menu_subitem %}
<li class="{{ subitem.type == 'divider' ? 'header' }} {{ app.request.query.get('menuIndex')|default(-1) == loop.parent.loop.index0 and app.request.query.get('submenuIndex')|default(-1) == loop.index0 ? 'active' }}">
{{ helper.render_menu_item(subitem, _entity_config.translation_domain|default('messages')) }}
</li>
{% endblock menu_subitem %}
{% endfor %}
</ul>
{% endif %}
</li>
{% endblock menu_item %}
{% endif %}
{% endfor %}
{% endblock main_menu %}
</ul>
{% block main_menu_after %}{% endblock %}
services:
# Twig extension
Acme\Twig\AcmeExtension:
tags:
- { name: twig.extension }
<?php
namespace Acme\Twig;
use Twig\Extension\AbstractExtension;
use Twig\TwigFunction;
/**
* Class AcmeExtension
*
* @package Acme\Twig
*/
class AcmeExtension extends AbstractExtension
{
/**
* @return array|\Twig_Function[]
*/
public function getFunctions()
{
return [
new TwigFunction('in_array', [$this, 'inArray', ['haystack', 'target']])
];
}
/**
* Check if at least one of $target is in $haystack
*
* @param array $haystack
* @param array $target
*
* @return bool
*/
public function inArray($haystack, array $target): bool
{
return count(array_intersect((array)$haystack, $target)) > 0;
}
}
@valentin-harrang
Copy link

Are you sure that "actions" works? Because "edit", "show" and "delete" are still visible and I still have access

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment