Symfony2: How to display admin-account name while impersonating user-account? - symfony

I want to display something like that:
Case 1: "logged in as USER"
# UserName [ logout ]
No problems here, i just do:
# {{ app.user.username}} [ logout ]
Case 2: "logged in as ADMIN"
# AdminName [ logout ]
The same works here:
# {{ app.user.username}} [ logout ]
Case 3: "logged in as ADMIN impersonating a USER"
AdminName # UserName [ return ]
Now thats a problem:
{{ ??..what here..?? }} # {{ app.user.username}} [ return ]
This is the only solution I know... it seems a lot of code for a sipmle displaying username :/
{# iterating through user roles to find ROLE_PREVIOUS_ADMIN #}
{% for role in app.security.token.roles %}
{% if role.source is defined %}
{{ role.source.user.username }}
{% endif %}
{% endfor %}
# {{ app.user.username }} [ return ]
Is there any other way? I need a pure TWIG solution -> this is supposed to be part of my main twig template (that is extended by all other templates) -> I can't add controller code to all actions, just to display username.

With the idea you have proposed above,.. can you not just create a custom twig extension that encompasses your logic from your twig template so that you can just call myCustomTwigFunction within your twig template and it will output the original users name?
See http://symfony.com/doc/current/cookbook/templating/twig_extension.html for more info about custom twig extensions
The code you'd have in your Twig extension file would be...
$roles = $this->container->get('security.context')->getToken()->getRoles();
foreach ($roles as $role) {
if (method_exists($role, 'getSource')) {
return ($role->getSource()->getUser()->getUsername());
}
}
Where $container is a class variable of the DI Container on your twig extension class

For anyone looking for a solution for Symfony 4.3/4.4/5.0:
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
{% for role in app.token.roles %}
{% if role.role == 'ROLE_PREVIOUS_ADMIN' %}
Admin username is {{ role.source.user.username }}
{% endif %}
{% endfor %}
{% endif %}
From Symfony 5.1 onwards, use IS_IMPERSONATOR in place of ROLE_PREVIOUS_ADMIN.

Related

Can not check new added role in Symfony2 twig

Under security.yml I added new role called ROLE_PUBLISHER
access_control:
- { path: ^/publisher/, roles: [ ROLE_PUBLISHER ] }
role_hierarchy:
ROLE_TC_ADMIN: [ ROLE_ALLOWED_TO_SWITCH ]
ROLE_PUBLISHER: [ ROLE_PUBLISHER_UNCONFIRMED ]
Role works fine, it is used on production env, but I just noticed that I cannot check in twig if logged user has correct role.
This one works fine, I am getting 'user' word
{% if is_granted('ROLE_USER') %}'user'{% else %}''{% endif %}
This one does not work (always empty string), even I am logged in as publisher
{% if is_granted('ROLE_PUBLISHER') %}'publisher'{% else %}''{% endif %}
Is it possible that user has two roles ROLE_USER and ROLE_PUBLISHER and twig always checking the first one?
Yep I had right, user has two roles, and twig has been checking the first one. So the solution is:
{% set user_role = '' %}
{% for user in app.user.roles %}
{% if user == 'ROLE_PUBLISHER' %}
{% set user_role = 'publisher' %}
{% endif %}
{% endfor %}

Sonata Admin - Disable list view

I'm facing a problem with Sonata Admin. Is there a way to disable the "list view" ? I would like to fetch the first entity in the database and to go on it when clicking on the link in the sidebar. But not for all entry.
Is there a clean way to do it ? (I have the idea to check the entity in a custom controller, and to redirect to list view or edit view depending on the entity, but that's not really clean)
If you want to do a custom query for your list view, you could override the createQuery method in your Admin class like this :
class EntityAdmin
{
public function createQuery($context = 'list')
{
$query = parent::createQuery($context);
$query->andWhere(
$query->expr()->eq($query->getRootAlias() . '.id', ':id')
);
$query->setParameter('id', 1);
return $query;
}
}
You will have only your first entity in your list view.
UPDATE
You could override the standard_layout.html.twig to change the link in your sidebar :
First you need to set where your template is located:
app/config/config.yml
sonata_admin:
templates:
layout: ApplicationSonataAdminBundle::standard_layout.html.twig
Change the behaviour of the sidebar, for Sonata Admin 2.3 this is how you do it :
src/Application/Sonata/AdminBundle/Resources/Views/standard_layout.html.twig l.224
<ul class="treeview-menu{% if active %} active{% endif %}">
{% for admin in group.items %}
{% if admin.code == 'sonata.admin.entity' and
admin.hasroute('edit') and
admin.isGranted('EDIT') %}
<li{% if app.request.get('_sonata_admin') == admin.code %} class="active"{% endif %}><i class="fa fa-angle-double-right"></i> {{ admin.label|trans({}, admin.translationdomain) }}</li>
{% else %}
{% if admin.hasroute('list') and admin.isGranted('LIST') %}
<li{% if app.request.get('_sonata_admin') == admin.code %} class="active"{% endif %}><i class="fa fa-angle-double-right"></i> {{ admin.label|trans({}, admin.translationdomain) }}</li>
{% endif %}
{% endif %}
{% endfor %}
</ul>
You must change 'sonata.admin.entity' by the identifier of your admin service.
Also if you want to remove access to the list you should add in your Admin class
use Sonata\AdminBundle\Route\RouteCollection;
class EntityAdmin
{
protected function configureRoutes(RouteCollection $collection)
{
$collection->remove('list');
}
}

Toggle html validation globally

I've made a couple of twig extensions but I'm stumped on this one.
I have the following template logic that I want to make into an extension.
I need reuse this logic into many different forms instead of copying and pasting the following code everywhere:
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
With the above code from the controller I can do the following to turn the html5 validator on and off:
$this->render(..., array(html5validation => false));
I want put the template logic into the twig extension below...
I just don't know if it's possible to implement what I've done above in a twig extension.
class HTML5Validation extends \Twig_Extension
{
public function getFunctions()
{
return array(
new \Twig_SimpleFunction('html5validation', array($this, 'setValidation')),
);
}
public function setValidation($boolean)
{
//Implement the same logic as the twig template.
}
public function getName()
{
return 'html5validator';
}
}
The short answer is no - you can't do this using a twig extension, it's not what they're meant for.
Looking at your template fragment I'd say you need to customise the form_start block. To do this see Symfony Form Theming and How to customise form rendering.
EDIT: This solution does not work if your customised code requires local twig variables - only global twig variables are available for form theming. You can define your own twig globals in config.yml or in a twig extension.
For example, to override form_start globally, you find the default definition of the form_start block in form_div_layout.html.twig, copy it into your own form theme file e.g. YourBundle/Form/fields.html.twig, modify it as required and and update the twig configuration to apply your form theme file. Something like this:
{# src/YourBundle/Form/fields.html.twig #}
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is not defined %}
{{ parent() }}
{% else %}
{% if html5validation %}
{{ parent() }}
{% else %}
{{ parent
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
{%- endblock form_start %}
Config:
# app/config/config.yml
twig:
form:
resources:
- 'YourBundle:Form:fields.html.twig'
I actually found a better way to do what I wanted.
As a plus it works globally instead of having to populate more fields into your controller!
In YourBundle/Resources/views/validation.toggle.html.twig
{% extends 'form_div_layout.html.twig' %}
{% block form_start -%}
{% if html5validation is defined and html5validation == false %}
{% set attr = attr|merge({'novalidate': 'novalidate'}) %}
{% endif %}
{{ parent() }}
{%- endblock form_start %}
Then if you want to turn off html5 validation across the whole website:
# app/config/config.yml
twig:
global:
html5validation: false
Or
Even better just use it in your dev_config.yml if you want validation on by default on production mode but the ability to toggle validation on and off for dev mode.
# app/config/dev_config.yml
twig:
global:
html5validation: false
resources:
- 'YourBundle::validation.toggle.html.twig'
Finally use it in your twig template normally:
{% form_theme your_form 'YourBundle::validation.toggle.html.twig' %}
form_start(your_form)
Reusable and non invasive, exactly like I wanted it. :)
I got the hint from:
https://github.com/symfony/symfony/issues/11409#issuecomment-49358377
In the absence of a more elegant solution, you can always put the twig fragment given in your question into a separate file and use twig include from your various forms. The included fragment has access to the variables from the surrounding context:
{# YourBundle/Resources/views/form_start.html.twig #}
{% if html5validation is not defined %}
{{ form_start(some_form) }}
{% else %}
{% if html5validation %}
{{ form_start(some_form) }}
{% else %}
{{ form_start
(
company, {'attr': {'novalidate': 'novalidate'}}
)
}}
{% endif %}
{% endif %}
Then in the twig file for the form:
{% include 'YourBundle::form_start.html.twig' %}
If you typically pass a 'form' variable into render() in your controller(s) then you can use that in your form_start fragment. Otherwise you can pass the appropriate form in as a variable:
{% include 'YourBundle::form_start.html.twig' with {'form': localForm} %}

Get ROLE of a user not logged in TWIG Symfony2

I would like to know how can i know if a user is granted when it's not the current user in twig.
I use this code for the current user:
{% if is_granted('ROLE_USER') %}
Delete
{% endif %}
But i would like to be able to do the same thing with ohter users that are not logged in at the moment.
Thank you.
Edit:
In fact i think there isn't a direct way with twig to test role of a user that is not authenticated.
So i did it directly in the twig template, test if a user is admin or not, then set var.
(in my question i was searching how to do in a list of users.)
{% set from_user_is_admin = false %}
{% for role in from_user.getRoles() %}
{% if role == 'ROLE_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% if role == 'ROLE_SUPER_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% endfor %}
{% if from_user_admin == false %}THIS USER IS NOT ADMIN{% endif %}
I think it would be much easier if you implemented an isGranted function in the User entity:
Class User implements UserInterface {
...
public function isGranted($role)
{
return in_array($role, $this->getRoles());
}
}
You can now easily check for granted roles in every layer of your application.
In PHP:
$user->isGranted("USER_ADMIN")
Or in Twig:
user.granted("USER_ADMIN")
If you need to check a role for the current user, you can do this in Twig:
app.user.granted("USER_ADMIN")
Note: the variable "app" is globally defined.
Note 2: this code may throw an exception if you use it outside the secured area of your app, since app.user would be NULL.
You can use similar statement to the above with "not" :
{% if not is_granted('ROLE_USER') %}
Delete
{% endif %}
or use else statement:
{% if is_granted('ROLE_USER') %}
Delete
{% else %}
{# something else for guest user, not logged in #}
{% endif %}
You should create either a twig macro, or a twig function.
Creating a macro is very simple, using your code:
{% macro admin_status(from_user) %}
{% set from_user_is_admin = false %}
{% for role in from_user.getRoles() %}
{% if role == 'ROLE_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% if role == 'ROLE_SUPER_ADMIN' %}{% set from_user_admin = true %}{% endif %}
{% endfor %}
{% if from_user_admin == false %}THIS USER IS NOT ADMIN{% endif %}
{% endmacro %}
You can then use it in the same file as {% _self.admin_status(user) %}. You may also move it to a separate file, and use twig's import tag to gain access to it.
Creating a twig function is a better option, for details see extending twig. It boils down to creating a regular function, that may be called from twig, so code like this becomes possible:
{% if user_is_admin(user) %}
You'll also need to read enabling custom twig extensions.
i did it this way, have this snippet in the global twig file, in my case layout.html.twig
{% set is_admin = false %}
{% if app.security.token.user.roles is iterable %}
{% for role in app.security.token.user.roles %}
{% if role == 'ROLE_ADMIN' or role == 'ROLE_SUPER_ADMIN' %}
{% set is_admin = true %}
{% endif %}
{% endfor %}
{% endif %}
then i can use anywhere
{% if is_admin %}....{% endif %}

How to identify if a user is being impersonated in Symfony2?

In an application built with Symfony2 we want superadmins to be able to impersonate other users. This is easily done by giving the superadmin user the ROLE_ALLOWED_TO_SWITCH role. The switching is implemented with a call to "somewhere?_switch_user=" as suggesed in the reference documentation.
The problem however, is to detect in a template if the current user is actually impersonated so as to print a link to "somewhere?_switch_user=_exit" on the page, thus enabling the impersonating user to return to her real user.
I haven't been using Symfony2 for a while so I'm not sure, but when you switch to another user you gain all roles assigned to that user and one extra role: ROLE_PREVIOUS_ADMIN. So I guess all you need to do is to use voter to check whether such a role is assigned to the current user using voter.
// Twig
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
EXIT
{% endif %}
// PHP
<?php if ($view['security']->isGranted('ROLE_PREVIOUS_ADMIN')): ?>
EXIT
<?php endif ?>
An example of how to get more details about the impersonator:
use Symfony\Component\Security\Core\Role\SwitchUserRole;
$sec = $this->get('security.context');
if($sec->isGranted('ROLE_PREVIOUS_ADMIN')) {
foreach($sec->getToken()->getRoles() as $role) {
if ($role instanceof SwitchUserRole) {
$admin_user = $role->getSource()->getUser();
}
}
}
You then have admin_user as the original user object. Remember to use the SwitchUserRole.
An example of how to display impersonator in twig:
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
{% for role in app.security.token.roles %}
{% if role.role == 'ROLE_PREVIOUS_ADMIN' %}
{{ role.source.user.username }}
{% endif %}
{% endfor %}
{% endif %}
If you need to test role from the previous admin user :
Working on Symfony 3.4
{% if is_granted('ROLE_PREVIOUS_ADMIN') %}
{% for role in app.token.roles %}
{% if role.role == 'ROLE_PREVIOUS_ADMIN' %}
{% for role_from_previous in role.source.roles if role_from_previous.role == "ROLE_DELETE" %}
{{ role.source.user.username }} has "ROLE_DELETE"
{% endfor %}
{% endif %}
{% endfor %}
{% endif %}

Resources