I am working on Symfony 3.4 project, and have been facing a weird issue. The web debug toolbar fails to load and instead gives an error "An error occurred while loading the web debug toolbar. Open the web profiler." Here is the screenshot
And when I click on Open the web profiler link it takes me to another exception page. Here is its screenshot
So after hours of debugging I was able to figure out that the issue is inside a
custom listener. It is registered in my services.yml as follows:
services:
language.kernel_request_listener:
class: TraceBundle\Listeners\LanguageListener
arguments:
- "#service_container"
tags:
- { name: kernel.event_listener, event: kernel.request, method: setLocale }
And here is the LanguageListener.php:
<?php
namespace TraceBundle\Listeners;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\HttpKernelInterface;
class LanguageListener{
private $token_storage;
private $securityContext;
private $container;
public function __construct(ContainerInterface $containerInterface)
{
$this->container = $containerInterface;
$this->securityContext = $this->container->get('security.authorization_checker');
$this->token_storage = $this->container->get('security.token_storage');
}
public function setLocale(GetResponseEvent $event)
{
if (HttpKernelInterface::MASTER_REQUEST !== $event->getRequestType()) {
return;
}
if ($this->securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$user = $this->token_storage->getToken()->getUser();
$userLocale = $user->getTenant()->getLanguage()->getValue();
$tenantid = $this->container->get('tenant_manager')->getTenantId($user);
$request = $event->getRequest();
$request->attributes->set('tenantid', $tenantid);
if ($userLocale) {
$request->setLocale($userLocale);
$translator = $this->container->get('translator');
$translator->setLocale($userLocale);
}
}
}
}
Now when I comment the following lines:
if ($this->securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
$user = $this->token_storage->getToken()->getUser();
$userLocale = $user->getTenant()->getLanguage()->getValue();
$tenantid = $this->container->get('tenant_manager')->getTenantId($user);
$request = $event->getRequest();
$request->attributes->set('tenantid', $tenantid);
if ($userLocale) {
$request->setLocale($userLocale);
$translator = $this->container->get('translator');
$translator->setLocale($userLocale);
}
the error goes away and the profiler loads as expected.
I've tried var_dump() after every line and all the values seem to be fine. the service tenant_manager works fine as well as the translator service. What am I missing here? Let me know if you need more code.
Thanks in advance!
EDIT: As requested here is my security.yml:
security:
# https://symfony.com/doc/current/security.html#b-configuring-how-users-are-loaded
providers:
in_memory:
memory: ~
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
# disables authentication for assets and the profiler, adapt it according to your needs
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
main:
pattern: ^/
form_login:
success_handler: authentication.handler.login_success_handler
provider: fos_userbundle
csrf_token_generator: security.csrf.token_manager
# logout: true
logout:
path: /logout
target: /login
anonymous: true
js_router:
pattern: ^/(js\/routing)
security: false
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/dashboard, role: ROLE_ADMIN }
- { path: ^/campaigns, role: ROLE_ADMIN }
- { path: ^/dashboard, role: ROLE_ADMIN }
- { path: ^/lives, role: ROLE_ADMIN }
- { path: ^/colleagues, role: ROLE_ADMIN }
- { path: ^/addcolleague, role: ROLE_ADMIN }
- { path: ^/adminpage, role: ROLE_ADMIN }
- { path: ^/test, role: ROLE_ADMIN }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
I had the exact same problem using a custom Listener with TokenStorageInterface.
Here's a scaled down version of my listener code:
class DatabaseUserAuthenticationListener {
private $authToken;
public function __construct(TokenStorageInterface $tokenStorage) {
if ($tokenStorage->getToken()) {
$this->authToken = $tokenStorage->getToken();
}
}
public function onKernelController(FilterControllerEvent $event) {
if ($this->authToken) {
$this->authToken->setAttribute("blah", true);
}
}
}
In my case the offending line was $this->authToken->setAttribute("blah", true);. $this->authToken ends up being null when _wdt routes are called (since they have no user context). At least that's my theory.
#Pavel is correct in that Symfony is setting the token to null between requests, although I don't think it's security: false that's doing it.
Checking that your token exists and is not null or empty before working with it (if ($this->authToken) {...}) fixes the issue (at least for me).
#utkarsh2k2, I'm sure you already fixed your issue... if not, you could try checking that $this->token_storage->getToken() has something in it before calling ->getUser().
I made some experiments with your code and found this:
Removing these lines solves the problem:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
also replacing them with these helps:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
anonymous: true
So I can conclude that security: false leads to setting security token to null behind the scenes.
So far I didn't locate this mechanism (will keep trying), so would be grateful for any help.
Another solution is to check if token is not null right inside of your listener:
if (null !== $this->token_storage->getToken()
&& $this->securityContext->isGranted('IS_AUTHENTICATED_REMEMBERED')) {
...
}
But it makes your code to care about situation caused by your dev config (dev firewall) so I decide it's not the best way.
Any comments/additions are welcome.
The solution for me was, when I star a proyect almost clean still have that problem:
"An error occurred while loading the web debug toolbar. Open the web profiler."
My Solution:
I added a .htaccess to the public directory
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*)$ ./index.php/$1 [QSA,L]
RewriteCond %{HTTP:Authorization} ^(.*)
RewriteRule .* - [e=HTTP_AUTHORIZATION:%1]
With that only the toolbar appear.
Related
In my app I have 2 parts, a frontend generated manually and a backend dashboard generated with Sonata admin. And I have an issue with the login of both parts:
app/config/security.yml
security:
encoders:
FOS\UserBundle\Model\UserInterface: sha512
m_and_m\UsuariosBundle\Entity\Usuarios: { algorithm: sha512, iterations: 10 }
m_and_m\ClientesBundle\Entity\Clientes: { algorithm: sha512, iterations: 10 }
access_control:
# URL of FOSUserBundle which need to be available to anonymous users
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
# Admin login page needs to be access without credential
- { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/login_check$, role: IS_AUTHENTICATED_ANONYMOUSLY }
# Secured part of the site
# This config requires being logged for the whole site and having the admin role for the admin part.
# Change these rules to adapt them to your needs
- { path: ^/admin/login ,role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/logout ,role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/* ,role: [ROLE_ADMIN, ROLE_SONATA_ADMIN,TOTAL] }
- { path: ^/usuarios/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/usuarios/registro, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/usuarios/*, roles: ROLE_USUARIO }
- { path: ^/clientes/*, roles: ROLE_CLIENTES }
role_hierarchy:
ROLE_ADMIN: [ROLE_USER, ROLE_SONATA_ADMIN,ROLE_USUARIO,ROLE_CLIENTE]
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
#SONATA:
#- ROLE_SONATA_PAGE_ADMIN_PAGE_EDIT # If you are using acl then this line must be commented
providers:
fos_userbundle:
id: fos_user.user_manager
chain_provider:
chain:
providers: [usuarios, clientes]
usuarios:
entity: { class: m_and_m\UsuariosBundle\Entity\Usuarios, property: email }
clientes:
entity: { class: m_and_m\ClientesBundle\Entity\Clientes, property: email }
firewalls:
# -> custom firewall for the admin area of the URL
admin:
pattern: /admin(.*)
context: user
form_login:
provider: fos_userbundle
login_path: /admin/login
use_forward: false
check_path: /admin/login_check
failure_path: /admin/login
logout:
path: /admin/logout
target: /admin
anonymous: true
# -> end custom configuration
# default login area for standard users
frontend:
pattern: ^/*
context: frontend
provider: chain_provider
anonymous: ~
form_login:
login_path: usuarios_login
check_path: usuarios_login_check
logout:
path: usuarios_logout
UsuariosBundle/Resources/config/services.yml
services:
usuarios.admin.usuarios:
class: m_and_m\UsuariosBundle\Admin\UsuariosAdmin
arguments: [~, m_and_m\UsuariosBundle\Entity\Usuarios, SonataAdminBundle:CRUD]
tags:
- {name: sonata.admin, manager_type: orm, group: Usuarios(Admin), label: Usuarios}
login_listener:
class: m_and_m\UsuariosBundle\Listener\LoginListener
arguments: [#security.context, #router]
tags:
- { name: kernel.event_listener, event: security.interactive_login }
- { name: kernel.event_listener, event: kernel.response }
Following the doc I have created a LoginListener in my usuariosbundle.
When I log in from the frontend all goes perfect. But the Sonata admin dashboard login goes to the LoginListener.php file and returns an error:
FatalErrorException: Error: Call to undefined method Application\Sonata\UserBundle\Entity\User::getNombre() in
C:\wamp\www\m_and_m\src\m_and_m\UsuariosBundle\Listener\LoginListener.php
line 24
LoginListener.php
<?php
namespace m_and_m\UsuariosBundle\Listener;
use Symfony\Componene\EventDispatcher\Event;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Routing\Router;
use Symfony\Component\HttpFoundation\RedirectResponse;
class LoginListener
{
private $contexto, $router, $usuario=null,$role=null;
public function __construct(SecurityContext $context, Router $router)
{
$this->contexto=$context;
$this->router = $router;
}
public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
{
$token = $event->getAuthenticationToken();
$this->usuario = $token->getUser()->getNombre();
$this->role = $token->getRoles();
$this->role=$this->role[0]->getrole();
if($this->role=='ROLE_CLIENTE' && $token->getUser()->getActivo()==false){
$this->usuario = null;
}
}
public function onKernelResponse(FilterResponseEvent $event)
{
if(null != $this->usuario)
{
if($this->role=='ROLE_USUARIO'){
$portada=$this->router->generate('portada_usuario');
}
else{
$portada=$this->router->generate('portada_cliente');
}
$event->setResponse(new RedirectResponse($portada));
$event->stopPropagation();
}
}
}
?>
And I donĀ“t know why is this happening, cause both providers are in different context. Do I need to do something more to separate both logins?
You are calling a method that does not exist in that entity. If you pay attention in the error you will see that the user class that comes from the token is not the one you think it is.
I am using the FOSOAuthBundle for my REST application
I would like most of my routes to require authorization however there are a few that should have public access
I have the following in my security.yml:
security:
encoders:
FOS\UserBundle\Model\UserInterface: bcrypt
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
oauth_token:
pattern: ^/login
security: false
api:
pattern: ^/
fos_oauth: true
stateless: true
anonymous: false
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: [ IS_AUTHENTICATED_FULLY ] }
For example:
I have a Products Entity and Controller
I would like the CRUD operations to be private except for Read
So: POST, PUT, DELETE on /products(/:id) should be private while GET should be public.
I have tried adding the following to the access_control:
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/products$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/, roles: [ IS_AUTHENTICATED_FULLY ] }
I thought this would open up all methods on /products but I get the error:
{
"error": "access_denied",
"error_description": "OAuth2 authentication required"
}
I have many entities and controllers I am trying to apply this to.
How would I go about opening up specific routes (including the method requirements)?
You can make new firewall with regex and set it like this. You have to put it in front of your api firewall in order to match this regex first.
api_anonym_area:
pattern: (^/api/products/.*)
methods: [GET]
security: false
Or you can make it
api_anonym_area:
pattern: (^/api/products/.*)
methods: [GET]
anonymous: true
with
access_control:
- { path: ^/api/products/.*, role: IS_AUTHENTICATED_ANONYMOUSLY}
In first case you wont have token, in in second case you will have token (its good when you expect authenticated or anonymous users to come).
To achieve this, best way would be to code the permissions in the controllers, I don't think this is possible via the security.yml configuration.
You should remove:
- { path: ^/, roles: [ IS_AUTHENTICATED_FULLY ] }
And manage permissions inside the controller actions, for example (taken from symfony documentation at http://symfony.com/doc/current/security.html)
public function updateProduct($id)
{
// The second parameter is used to specify on what object the role is tested.
$this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
// Old way :
// if (false === $this->get('security.authorization_checker')->isGranted('ROLE_ADMIN')) {
// throw $this->createAccessDeniedException('Unable to access this page!');
// }
// ...
}
guys!
I'am trying to configure user system (FOSUserBundle) for my Symfony app. All was going Ok until I tried to allow access to whole site and restrict access to some routes using access control in security configuration file.
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
default_target_path: /
logout: true
anonymous: true
access_control:
- { path: ^/$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/profile$, roles: ROLE_USER }
- { path: ^/admin$, roles: ROLE_ADMIN }
I've tried also
#
- { path: ^/.*, roles: IS_AUTHENTICATED_ANONYMOUSLY }
#
and when i go to index page (/), it redirects me to login page. I want to have index page visible for all visitors, not only for those who logged in.
Just can't figure out this moment. Thank you for answers.
try this:
access_control:
- { path: ^/profile$, roles: ROLE_USER }
- { path: ^/admin$, roles: ROLE_ADMIN }
- { path: ^/, roles: IS_AUTHENTICATED_ANONYMOUSLY }
Your problem may be related to trying to specify the role as an array of role but you give only one value:
- { path: ^/*, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/profile$, role: ROLE_USER }
- { path: ^/admin$, role: ROLE_ADMIN }
for more roles you need the []
I resolved the issue. Sorry for my dumbness, i've been wandering for 2 days why this happen.
Redirect was from controller. My mistake, sorry. Thank you for answers anyway.
public function startAction()
{
$user = $this->getUser();
if ($user) {
return $this->render('HellfireCommonBundle:Index:index.html.twig', array('user' => $user));
} else {
return $this->redirect($this->generateUrl('fos_user_security_login'));
}
}
I'd like to redirect user to login page when nobody is not logged in. I wrote lister to that, but all the time I'm getting ERR_TOO_MANY_REDIRECTS error.
Maybe is another method to achieve it globaly, and redirect user. Checking if user is logged in every controller is not a solution.
Listener\AccessListener.php
namespace Main\UserBundle\Listener;
use Symfony\Component\HttpKernel\Event\GetResponseForExceptionEvent;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Security\Core\SecurityContext;
class AccessListener{
private $security;
private $router;
public function __construct($security, $router){
$this->security = $security;
$this->router = $router;
}
public function onKernelRequest(GetResponseEvent $event){
if ($event->isMasterRequest()) {
if( $this->security->isGranted('IS_AUTHENTICATED_REMEMBERED') ){
$url = $this->router->generate('fos_user_security_login');
$event->setResponse(new RedirectResponse($url));
}
}
}
}
security.yml
security:
access_denied_url: /login
encoders:
FOS\UserBundle\Model\UserInterface: sha512
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
providers:
fos_userbundle:
id: fos_user.user_provider.username
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
always_use_default_target_path: false
default_target_path: /
logout: true
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
How do I correct it?
Your listener is checking whether a user is logged in and then, if they are not, they are being forwarded to the login page. At which point the listener checks whether the user is logged in... and so on.. and so on.
To stop this redirect loop you could check whether the current route being requested is the route that you are forwarding to, like so...
public function onKernelRequest(GetResponseEvent $event){
if ($event->isMasterRequest()) {
$loginRoute = 'fos_user_security_login';
$request = $event->getRequest();
// Return if current route and login route match
if ($request->get('_route') === $loginRoute) {
return;
}
if( $this->security->isGranted('IS_AUTHENTICATED_REMEMBERED') ){
$url = $this->router->generate($loginRoute);
$event->setResponse(new RedirectResponse($url));
}
}
}
However, a better way of doing this would be to add the root to the access control section needing a logged in user. This would allow you to have a better control over accessible and inaccessible paths.
access_control:
... current stuff ...
- { path: ^/, role: ROLE_USER }
You need to check whether the current security context holds (or in your case if it does not hold) a fully authenticated user session as follows:
if( false == $this->security->isGranted('IS_FULLY_AUTHENTICATED') ){
I am building a site using Symfony2 and it will be a white-label type of site, where multiple domains map to the same server. So coolsite.customer1.com and aservice.customer2.com would map to the same site, but would need to appear different to the end user. I already solved for the domains, and loading the unique configurations as a service.
With the FOS UserBundle setup and running with a custom user (that has the domain_id stored in it), registration, login, etc works fine except that users from domain1 can login to domain2 also. This is expected in the FOS UserBundle. I need to make modifications to the bundle so that it only will authenticate users on the domain they are assigned to.
I have created a userProvider that extends the original userProvider in FOS and have overridden the loadUserByUsername method to also check the domain. See below:
use FOS\UserBundle\Security\UserProvider as FOSProvider;
use Symfony\Component\DependencyInjection\ContainerInterface;
use FOS\UserBundle\Model\UserManagerInterface;
use Me\CoreBundle\Models\Core;
class UserProvider extends FOSProvider {
/**
*
* #var ContainerInterface
*/
protected $container;
public function __construct(UserManagerInterface $userManager, ContainerInterface $container) {
parent::__construct($userManager);
$this->container = $container;
}
/**
* {#inheritDoc}
*/
public function loadUserByUsername($username)
{
$core = $this->container->get('me_core');
/* #var $core Core */
$user = $this->findUserBy(array(
'username'=>$username,
'domain_id'=>$core->getDomainMap()->getId(),
));
if (!$user) {
throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
}
return $user;
}
public function findUserBy(array $criteria) {
return $this->userManager->findUserBy($criteria);
}
}
I have configured the service with the following.
services:
me.security.authentication.userprovider:
class: Me\UserBundle\Security\UserProvider
arguments:
- #fos_user.user_manager
- #service_container
My security.yml looks like this:
security:
providers:
me.security.authentication.userprovider:
id: fos_user.user_provider.username
encoders:
FOS\UserBundle\Model\UserInterface: sha512
firewalls:
main:
pattern: ^/
form_login:
provider: fos_userbundle
csrf_provider: form.csrf_provider
logout: true
anonymous: true
access_control:
- { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/_wdt, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/public, role: IS_AUTHENTICATED_ANONYMOUSLY }
- { path: ^/admin/, role: ROLE_ADMIN }
- { path: ^/, role: ROLE_USER }
role_hierarchy:
ROLE_ADMIN: ROLE_USER
ROLE_SUPER_ADMIN: ROLE_ADMIN
What happens when I try to access the site is an exception. "ServiceNotFoundException: The service "security.authentication.manager" has a dependency on a non-existent service "security.user.provider.concrete.fos_userbundle"."
I based my modifications on This Cookbook Recipe
Any ideas? I am thoroughly stumped on this.
I was able to get it to work. Turns out I needed to make the "id" the same as the name of the service I was using. The commented lines are the originals that came with the bundle.
security:
providers:
me.security.authentication.userprovider:
id: me.security.authentication.userprovider
#fos_userbundle:
#id: fos_user.user_provider.username