How to add item_permission on delete action in easyadmin? - symfony

I use EasyadminBundle for the Backend of a Symfony application.
Two type of users have access to the back-end and I'd like to keep the right to delete to a small number of persons granted with ROLE_ADMIN.
I'd like to use item_permission parameter as for the other actions (such as show or list) :
Lieu:
class: App\Entity\Lieu
list:
item_permission: ROLE_ENCADRANT
delete:
item_permission: ROLE_ADMIN
But it's not working and I can still delete user when I'm logged with ROLE_ENCADRANT. Is there another solution ?
I currently accomplish it with:
Lieu:
class: App\Entity\Lieu
list:
item_permission: ROLE_ENCADRANT
action: ['-delete']
help: "the delete button is accessible in <b>Edit</b> view"
form:
item_permission: ROLE_ADMIN
I'm just looking for a 100% configuration solution, more elegant than mine.

Take a look at adding an action in the docs. The action can be tied to a route, which allows specifying what role may perform the action. The downside is that the list view button is present regardless of role. You can add a flash message to advise the user whether they have permission.
Here's an example from a project. Not quite what you're looking for but may get you started:
easyadmin.yaml:
Admin:
class: App\Entity\Admin
disabled_actions: ['new', 'edit']
list:
actions:
-
name: 'admin_enabler'
type: 'route'
label: 'Enable/Disable'
controller:
/**
* #Route("/enabler", name = "admin_enabler")
*/
public function enabler(Request $request)
{
$em = $this->getDoctrine()->getManager();
$id = $request->query->get('id');
$admin = $em->getRepository(Admin::class)->find($id);
$enabled = $admin->isEnabled();
if (!$admin->isActivator() && !$admin->hasRole('ROLE_SUPER_ADMIN')) {
$admin->setEnabled(!$enabled);
$em->persist($admin);
$em->flush();
} else {
$this->addFlash('danger', $admin->getFullName() . ' cannot be disabled');
}
return $this->redirectToRoute('easyadmin', array(
'action' => 'list',
'entity' => $request->query->get('entity'),
));
}

Related

Setup two different from_email in FOS_USER configuration

I'm using symfony 2.3 version and I want to configure two different from_email in fos_user configuration how is it possible and where to set my configuration.
I want to send welcome email after registration normal user using normaluser#gmail.com and send addition user welcome email using additionaluser#gmail.com
Plz suggest any solution.
You can do it by Using A Custom Mailer.
Create a custom service
Example:
<?php
namespace AppBundle\Mailer;
// implement all the needed methods
class CustomMailer implements MailerInterface
{
public function sendConfirmationEmailMessage(UserInterface $user)
{
$template = $this->parameters['confirmation.template'];
$url = $this->router->generate('fos_user_registration_confirm', array('token' => $user->getConfirmationToken()), UrlGeneratorInterface::ABSOLUTE_URL);
$rendered = $this->templating->render($template, array(
'user' => $user,
'confirmationUrl' => $url,
));
// implement the logic that decides which from_email to use
// change the from_email accordingly
$this->sendEmailMessage($rendered, $this->parameters['from_email']['confirmation'], (string) $user->getEmail());
}
}
and update the fos_user configuration to use your custom mailer
fos_user:
# ...
service:
mailer: app.custom_fos_user_mailer
Reference links:
http://symfony.com/doc/current/bundles/FOSUserBundle/emails.html#using-a-custom-mailer
https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Mailer/Mailer.php

Symfony2 homepage different for authenticated users

How can I define my Symfony2 routes to show a different homepage for authenticated users and non-authenticated users? For example, I want to do something like this in my routing.yml file:
homepage_authenticated:
path: /
defaults:
_controller: AcmeBundle:Home:homeAuthenticated
requirements:
user: is_authenticated_remembered # <--- this part here
homepage:
path: /
defaults:
_controller: AcmeBundle:Home:home
Now obviously this doesn't work because I just invented it, but I'm sure there must be a way to do this, but I can't find it. I have an idea Expressions may be the solution to this somehow, but I can't find any examples of actually using them, anywhere.
As Malcom suggested in the comment, it is better to handle redirects/page-rendering based on user's authentication status in the controller.
The security context saves the role related data and the authentication status. You can redirect your users to different pages by checking
$this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY') and $this->get('security.context')->isGranted('ROLE_NAME').
For example:
public function homeAction()
{
$em = $this->getDoctrine()->getEntityManager();
if ($this->get('security.context')->isGranted('IS_AUTHENTICATED_FULLY')) {
//Path handling for authenticated users
if ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
return $this->redirect($this->generateUrl('admin_homepage'));
}
if ($this->get('security.context')->isGranted('ROLE_USER')) {
return $this->render('VenomCoreBundle:Default:home.html.twig', array(
'notifications' => $notifications,
'unApprovedCount' => $unApprovedCount,
'status' => $stats,
));
}
}
//non authenticated users are redirected here
return $this->render('VenomCoreBundle:Default:login.html.twig');
}

Child Admin route is not being generated - Sonata Admin Bundle

I'm trying to set up an Admin as a child of an other Admin in Sonata Admin Bundle.
I have 2 Admin classes:
CategoryAdmin
This class contains the following method
protected function configureSideMenu(MenuItemInterface $menu, $action, AdminInterface $childAdmin = null)
{
$id = $this->getRequest()->get('id');
$menu->addChild(
$this->trans('Projects'),
array('uri' => $this->getChild('sonata.admin.project')->generateUrl('list', array('id' => $id)))
);
}
ProjectAdmin
This class contains protected $parentAssociationMapping = 'category';
category is the property in the model class representing the ManyToOne association.
I added the following lines to my service configuration for CategoryAdmin
calls:
- [ addChild, ["#sonata.admin.project"]]
The routes for the child Admin are not being generated with this configuration. The link in the SideMenu (top menu) points to /admin/project/list?childId=1&id=1
Here is the output of the children of CategoryAdmin with dump()
array:1 [▼
"sonata.admin.project" => ProjectAdmin {#406 ▶}
]
This means that the configuration for my child admin seems to be correct. I have no idea, why the routes for the child admin are not being generated.
I hope somebody can give me a hint, what the problem could be.
Note for next gen sonata coders:
If your route is not being generated, first check you didn't do:
protected function configureRoutes(RouteCollection $collection)
{
//clear all routes except given !!!
$collection->clearExcept(array('list', 'show'));
}
It costs me two days...
Do you have the $baseRouteName and $baseRoutePattern overriden in your admin class ?
If you do, Sonata will generate both child and parent routes with the same name resulting in the parent routes overriding the child ones.
I bumped into this issue while solving the problem for myself and decided to share the solution, which costed me several debugging hours...
The only way to generate a proper uri in this case is to use low-level routeGenerator which doesn't make any sonata suggestions, made inside generateMenuUrl method.
First you have to debug the routes, you have in your app (including autogenerated by sonata).
php bin/console debug:router
For example I have 3 nesting levels
hall -> seats scheme -> sector
And my routes are following:
adminHall_list ANY ANY ANY /admin/hall/list
adminHall_create ANY ANY ANY /admin/hall/create
adminHall_edit ANY ANY ANY /admin/hall/{id}/edit
adminHall_delete ANY ANY ANY /admin/hall/{id}/delete
adminHall_adminScheme_list ANY ANY ANY /admin/hall/{id}/scheme/list
adminHall_adminScheme_create ANY ANY ANY /admin/hall/{id}/scheme/create
adminHall_adminScheme_edit ANY ANY ANY /admin/hall/{id}/scheme/{childId}/edit
adminHall_adminScheme_delete ANY ANY ANY /admin/hall/{id}/scheme/{childId}/delete
adminHall_adminScheme_adminSector_list ANY ANY ANY /admin/hall/{id}/scheme/{childId}/sector/list
adminHall_adminScheme_adminSector_create ANY ANY ANY /admin/hall/{id}/scheme/{childId}/sector/create
adminHall_adminScheme_adminSector_edit ANY ANY ANY /admin/hall/{id}/scheme/{childId}/sector/{childChildId}/edit
adminHall_adminScheme_adminSector_delete ANY ANY ANY /admin/hall/{id}/scheme/{childId}/sector/{childChildId}/delete
In admin classes baseRouteName and baseRoutePattern has been overridden.
// HallSchemeAdmin.php
$this->baseRouteName = 'adminScheme';
$this->baseRoutePattern = 'scheme';
To generate a most deep listing url:
$url = $admin->getRouteGenerator()->generate('adminHall_adminScheme_adminSector_list', [
'id' => $admin->getRequest()->get('id'),
'childId' => 555, // put required id
]);
It will produce the url like this:
/admin/hall/495/scheme/555/sector/list
If you need edit url, you have to provide childChildId param too:
$url = $admin->getRouteGenerator()->generate('adminHall_adminScheme_adminSector_edit', [
'id' => $admin->getRequest()->get('id'),
'childId' => 555,
'childChildId' => 12345
]);
The result is:
/admin/hall/495/scheme/555/sector/12345/edit

Symfony2 Doctrine Form - How to get the project object in another class?

Users can comment on different projects (OneToMany relation Project -> Comment). If a comment is created I need to know which user did it and for which project. I already figured out how to get the current user, but I don't know how to get the project object.
Here is the createAction controller, my question now, how can I access the current project which is beeing displayed.
/**
* Creates a new Comment entity.
*
*/
public function createAction(Request $request)
{
$entity = new Comment();
$form = $this->createCreateForm($entity);
$form->handleRequest($request);
if ($form->isValid()) {
$entity->setUser($this->get('security.context')->getToken()->getUser());
$em = $this->getDoctrine()->getManager();
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl('dbe_comment_show', array('id' => $entity->getId())));
}
return $this->render('DbeDonaciBundle:Comment:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
Thanks in advance!
Changes I made:
dbe_comment_create:
pattern: /project/{id}/comment
defaults: { _controller: "DbeDDDDBundle:Comment:create" }
requirements: { _method: post }
Twig:
{{ render(controller('DbeDDDDBundle:Comment:new', { 'id': entity.id})) }}
Here is the error message I get, because I'm rendering the new method and not the create.
An exception has been thrown during the rendering of a template ("Some mandatory parameters are missing ("id") to generate a URL for route "dbe_comment_create".") in DbeDDDDDBundle:Project:show.html.twig at line 305.
You also have a shortcut for current user: $this->getUser()
As for project. You mast pass it somehow. Two ways come to my mind.
Add a hidden field containing project id to your form.
Have your project id in route (and therefor as action parameter too).
Generally I would opt for #2 since it follows Restful conventions. Make your create_comment path: projects/{projectId}/comments POST.
That way you will have $projectId parameter available in your action.

Behat authenticate Symfony2 user

I'm using Behat in Symfony2 / Doctrine2. Now, I have this scenario that boils down to the fact that "if I'm logged in and I go to /login, I shoud go to / instead":
#login
Scenario: Go to the login page while being logged in
Given I am logged in
When I go to "/login"
Then I should be on "/"
For the #login, I created the following:
/**
* #BeforeScenario #login
*/
public function loginUser()
{
$doctrine = $this->getContainer()->get('doctrine');
$userRepository = $doctrine->getRepository('MyTestBundle:User');
$user = $userRepository->find(1); // 1 = id
$token = new UsernamePasswordToken($user, NULL, 'main', $user->getRoles());
$this->getContainer()->get('security.context')->setToken($token);
}
In the "when I go to /login" code (the controller gets called), the token seems gone (not what I intended):
/**
* #Route("/login", name="login")
*/
public function loginAction()
{
$token = $this->get('security.context')->getToken();
$fd = fopen('/tmp/debug.log', 'a');
fwrite($fd, $token);
// prints 'AnonymousToken(user="anon.", authenticated=true, roles="")'
...
But in the FeatureContext, it seems to stick around (the way I hoped it would work). In the "Given I am logged in":
/**
* #Given /^I am logged in$/
*/
public function iAmLoggedIn()
{
$token = $this->getContainer()->get('security.context')->getToken();
$fd = fopen('/tmp/debug.log', 'a');
fwrite($fd, $token);
// prints 'UsernamePasswordToken(user="admin", authenticated=true, roles="ROLE_ADMIN")'
...
I run behat like this:
app/console -e=test behat
I also did this in the controller to be sure it's test:
fwrite($fd, $this->get('kernel')->getEnvironment());
// prints 'test'
Any clue how to authenticate a user? I will have to test a lot of admin pages, so it would be nice if I could hook the login into #BeforeSuite, #BeforeFeature (or #BeforeScenario ...) so that I don't get blocked.
(Suggestions on disabling the authentication mechanism for testing, or a way to stub/mock a user are also welcome.)
Oh my. It doesn't work because the DIC inside your FeatureContext isn't shared with your app - your app has separate kernel and DIC. You can get it through Mink. Or, you can simply do it right way :-)
Right way means, that every part of behavior, that is observable by the enduser, should be described inside *.feature, not inside FeatureContext. It means, that if you want to login a user, you should simply describe it with steps (like: "i am on /login", "and i fill in username ...", "i fill in password" and stuf). If you want to do it in multiple times - you should create a metastep.
Metasteps are simply steps, that describe multiple other steps, for example - "i am logged in as everzet". You could read bout them here: http://docs.behat.org/guides/2.definitions.html#step-execution-chaining
Here is an solution for login with OAuth I've used. After number of times of searching for the answer and landing on this page I thought it would be great to share the solution. Hopefully it will help someone.
Background: Symfony2 App using HWIOAuthBundle, hooked up to some OAuth2 provider.
Problem: How do I implement Given I'm logged in when Behat context in not shared with Symfony context?
Solution:
HWIOAuthBundle uses #buzz service for all API calls to OAuth providers. So all you need to do is replace Buzz client with your implementation which doesn't call external services, but returns the result straight away. This is my implementation:
<?php
namespace Acme\ExampleBundle\Mocks;
use Buzz\Client\ClientInterface;
use Buzz\Message\MessageInterface;
use Buzz\Message\RequestInterface;
class HttpClientMock implements ClientInterface
{
public function setVerifyPeer()
{
return $this;
}
public function setTimeout()
{
return $this;
}
public function setMaxRedirects()
{
return $this;
}
public function setIgnoreErrors()
{
return $this;
}
public function send(RequestInterface $request, MessageInterface $response)
{
if(preg_match('/\/oauth2\/token/', $request->getResource()))
{
$response->setContent(json_encode([
'access_token' => 'valid',
'token_type' => 'bearer',
'expires_in' => 3600
]));
}
elseif(preg_match('/\/oauth2\/me/', $request->getResource()))
{
$response->setContent(json_encode([
'id' => 1,
'username' => 'doctor',
'realname' => 'Doctor Who'
]));
}
else throw new \Exception('This Mock object doesn\'t support this resource');
}
}
Next step is to hijack the class used by HWIOAuthBundle/Buzz and replace it with the implementation above. We need to do it only for test environment.
# app/config/config_test.yml
imports:
- { resource: config_dev.yml }
parameters:
buzz.client.class: Acme\ExampleBundle\Mocks\HttpClientMock
And finally, you need to set require_previous_session to false for test environment - therefore I suggest to pass it as parameter.
# app/config/security.yml
security:
firewalls:
secured_area:
oauth:
require_previous_session: false
Now you can implement your step like this.
Specification:
Feature: Access restricted resource
Scenario: Access restricted resource
Given I'm logged in
When I go to "/secured-area"
Then I should be on "/secured-area"
And the response status code should be 200
Implementation:
<?php
/**
* #Given /^I\'m logged in$/
*/
public function iMLoggedIn()
{
$this->getSession()->visit($this->locatePath('/login/check-yourOauthProvider?code=validCode'));
}
The code you're passing is not relevant, anything you pass will be OK as it's not being checked. You can customise this behaviour in HttpClientMock::send method.
http://robinvdvleuten.nl/blog/handle-authenticated-users-in-behat-mink/ is simple, clean article on how to create a login session and set the Mink session cookie so that the Mink session is logged in. This is much better than using the login form every time to login a user.
It’s ok to call into the layer “inside” the UI layer here (in symfony: talk to the models).
And for all the symfony users out there, behat recommends using a Given step with a tables arguments to set up records instead of fixtures. This way you can read the scenario all in one place and make sense out of it without having to jump between files:
Given there are users:
| username | password | email |
| everzet | 123456 | everzet#knplabs.com |
| fabpot | 22#222 | fabpot#symfony.com |

Resources