I want to validate a User entity with custom constraint & validator. So far it's working when triggered by form workflow, but if I trigger it manually, I loose one relation I setup before calling validation :
UserController :
$user = new User();
$user->setRoles($roles);
$user->setSite($site);
...
$violations = $this->container->get('validator')->validate($user);
User entity with Site relation :
/**
* #var Site the site linked to the entity
* #ORM\ManyToOne(targetEntity="LCH\MultisiteBundle\Entity\Site", cascade={"all"})
* #ORM\JoinColumn(name="site_id", referencedColumnName="id", onDelete="CASCADE")
*/
protected $site;
Validator :
public function validate($user, Constraint $constraint)
{
$email = $user->getEmail();
// $site var is null while other "direct fields are filled
$site = $user->getSite();
$roles = $user->getRoles();
$username = $user->getUsername();
How can I manually validate this entity using preceeding set relation?
My problem found its origin in Symfony2 form validation structure. : as $form->handleRequest($request) indeed validates form, all hooked validators (groups, custom constrains and callbacks) are triggered.
My $site was null because validator was fired long before I set up my $user->site attribute...
// sumbit in this method trigger validation too early in my needs
$form->handleRequest($request);
$em = $this->getDoctrine()->getManager();
if ($form->isSubmitted() && $form->isValid() && !$isAjax) {
// custom processes to decide what to create
...
// Here is the user creation
$user->setRoles($roles);
$user->setSite($site);
...
// And the check
$violations = $this->container->get('validator')->validate($user);
}
The solution here lied in disabling validation groups in the main type. Doing so, $user is fully passed to validator.
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'validation_groups' => false,
));
}
The main drawback with this system is that I have to manually trigger validation for this very type in all interactions, but this type is complex enough to make sense here.
Related
I have built a form using symfony's form builder to create new entries with the doctrine entity manager, but upon creating new entries, I occasionally run into a Integrity constraint violation: 1062 Duplicate entry'. I would like to properly handle this exception using the DRY principles, what is the best way to do this gracefully?
Preferably I would have a warning show even before submitting but after submission would be acceptable also.
FlightController.php
/**
* #Route(path="/new", name="flight_new", methods={"GET", "POST"})
*/
public function new(Request $request)
{
//Create New Flight
$flight = new Flight($this->getDoctrine()->getManager());
$form = $this->createForm(FlightType::class, $flight);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$flight = $form->getData();
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($flight);
$entityManager->flush();
return $this->redirectToRoute("flight_list");
}
return $this->render('flight/new.html.twig', array('form' => $form->createView()));
}
Flight.php The constraint is a compound key:
/**
* #ORM\Entity(repositoryClass="App\Repository\FlightRepository")
* #ORM\Table(name="flight",
* uniqueConstraints={#ORM\UniqueConstraint(name="flight",columns={"ap_from","ap_to"})}
* )
*/
class Flight
{
Thanks in advance
I have a Symfony 3 app that uses Doctrine ORM for Entity management. Currently, I am working on enabling CRUD support. I've already found out that I can use security voters to restrict access to entities or controllers. For example, I configured it the way that only admins can create, update or delete entities of type A.
For instances of my entity type B I also want to give the respective owner the power to update (not create or delete), which I managed to do easily. However, an owner shouldn't be allowed to modify all of the entity's properties - just some of them. How can I realize this with Symfony? Also, I am using the Form Bundle to create and validate forms.
EDIT: I added some related code. The controller invokes denyAccessUnlessGranted, which triggers the voter. Just to clarify, that code works fine already. My question is related to code I don't yet have.
Controller:
public function editAction(Request $request, int $id) {
$em = $this->getDoctrine()->getManager();
$project = $em->getRepository(Project::class)->findOneBy(['id'=>$id]);
$this->denyAccessUnlessGranted(ProjectVoter::EDIT, $project);
$users = $em->getRepository(EntityUser::class)->findAll();
$groups = $em->getRepository(Group::class)->findAll();
$tags = $em->getRepository(Tag::class)->findAll();
$form = $this->createForm(ProjectType::class, $project, [
'possibleAdmins' => $users,
'possibleRequiredGroups' => $groups,
'possibleTags' => $tags,
]);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$project = $form->getData();
$em->flush();
return $this->redirectToRoute('projects_show', ['id'=>$project->getId()]);
}
return $this->render('project/editor.html.twig',
['project'=>$project, 'form'=>$form->createView()]);
}
Voter:
protected function voteOnAttribute($attribute, $subject, TokenInterface $token) {
/** #var UserInterface $user */
$user = $token->getUser();
if (!$user instanceof UserInterface) {
// the user must be logged in; if not, deny access
return false;
}
else if ($this->decisionManager->decide($token, ['ROLE_ADMIN'])) {
return true; // system-wide admins shall always have access
}
switch($attribute) {
case self::SHOW:
return ($subject->isVisible() || $subject->getAdmins()->contains($user);
case self::EDIT:
return $subject->getAdmins()->contains($user);
case self::REMOVE:
return false;
}
return false;
}
As far as I know there is no access functionality specifically related to individual properties. Of course as soon as I post this, someone else will come by with exactly that.
What you might consider doing is to define two edit roles, EDIT_BY_ADMIN and EDIT_BY_OWNER. You could then test the condition and select which form type to use.
$projectTypeClass = null;
if ($this->isGranted(ProjectVoter::EDIT_BY_ADMIN,$project)) {
$projectTypeClass = ProjectAdminType::class);
}
elseif ($this->isGranted(ProjectVoter::EDIT_BY_OWNER,$project)) {
$projectTypeClass = ProjectOwnerType::class);
}
if (!$projectTypeClass) {
// throw access denied exception
}
$form = $this->createForm($projectTypeClass, $project, [
And that should do the trick. There are of course many variations. You could stick with one project type and do the access testing within the type class though that would require a form listener.
If you need more granularity then you could instead add some EDIT_PROP1, EDIT_PROP2 type roles.
And of course if you were really into it then you could move some of the access code into a database of some sort. Or maybe take a look at some of the Access Control List components out there.
I came up with this solution in the end:
Instead of having multiple FormTypes I stuck with only a single one and ended up enabling or disabling the property's form field based on the result of the voter. For that I defined a new switch case as Cerad suggested (named ProjectVoter::MODIFY_PROTECTED_PROPERTY in this answer for demonstration purposes) and added the business logic per my liking.
I just enabled or disabled the form field because I actually want the user to see that he/she can't edit that property. But it would likely easily be possible to not add the field in the first place as well, so it's not visible.
Form Type:
Info: $this->tokenStorage and $this->accessDecisionManager are injected services ("security.token_storage" and "security.access.decision_manager" respectively).
public function buildForm(FormBuilderInterface $builder, array $options) {
$token = $options['token'] ?? $this->tokenStorage->getToken();
$project = $builder->getData();
$builder
->add('name')
// ...
->add('protectedProperty', null, [
'disabled' => !$this->accessDecisionManager->decide($token, [ProjectVoter::MODIFY_PROTECTED_PROPERTY], $project),
])
;
}
I also added an option to the form type called token in its configureOptions function which defaults to null, so that the form can be built for an arbitrary user instead of the one currently logged-in, if required.
How can I handle request data from the $_POST data itself. I mean if I try to handle the form like this: $form->handleRequest($request);, Symfony would try to get the data from $_POST['form_classname'], but I want to fill my form class straight from base $_POST variables.
Actually I want to handle the information from the outer site. And I have to develop something like an API. But without authorization, tokens, etc...
So I decided to build the form with some properties I need. After validation the form might do some logic.
Here is an example of $_POST I have to handle
Function=TransResponse&RESULT=0&RC=00&AUTHCODE=745113
As you can see, there is no form name in request. The $form->handleRequest($request); works only if the request was like an
[form_name][Function]=TransResponse&[form_name][RESULT]=0&[form_name][RC]=00&[form_name][AUTHCODE]=745113
But I can't change the request format.
Just put in your form class
/** #inheritdoc */
function getBlockPrefix() {
return '';
}
Here is the information about this method Documentation
Use
$this->get('form.factory')->createNamed('')
$this->get('form.factory.)->createNamedBuilder('')
to create a Form or FormBuilder respectively that uses the whole $_POST/$_GET array for its parameters.
Example:
/**
* #Route("/testRoute")
* #param Request $request
* #return Response
*/
public function testAction(Request $request): Response
{
$form = $this->get('form.factory')->createNamedBuilder('', FormType::class, null, ['csrf_protection' => false])
->add('text', TextType::class)
->setMethod('GET')
->getForm();
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
return new Response($form['text']->getData());
}
return new Response('Submit me');
}
I have a page with a form and want to know if it is possible to access it using GET, but only allow logged in users to POST to it.
I know this can be done in security.yml, but am not sure how to do it with annotations.
/**
* #param Request $request
* #return Response
* #Security("has_role('ROLE_USER')")
* #Method(methods={"POST"})
*/
public function calculatorAction(Request $request)
{
$form=$this->createForm(new CallRequestType(),$callReq=new CallRequest());
$form->handleRequest($request);
if($form->isValid()){
//blabla
}
return $this->render('MyBundle:Pages:calculator.html.twig', array('form' => $form));
}
This will secure the whole function, but I want to access it, just not POST to it without being logged in. An alternative would be to check if there is a logged in user in the $form->isValid() bracket. But I'm still wondering if it can be done with annotations.
You could do something like this.
You can allow both method types anonymously, and check just inside the controller to see if the user is authenticated and is POSTing.
(You don't state which version of symfony you're using, so you might have to substitute the authorization_checker (2.8) for the older security.context service)
/**
* #param Request $request
* #return Response
*
* #Route("/someroute", name="something")
* #Method(methods={"POST", "GET"})
*/
public function calculatorAction(Request $request)
{
if ( !$this->get('security.authorization_checker')->isGranted('IS_AUTHENTICATED_FULLY') && $request->getMethod() == 'POST') {
throw new AccessDeniedHttpException();
}
$form=$this->createForm(new CallRequestType(),$callReq=new CallRequest());
$form->handleRequest($request);
// you also need to check submitted or youll fire the validation on every run through.
if($form->isSubmitted() && $form->isValid()){
//blabla
}
return $this->render('MyBundle:Pages:calculator.html.twig', array('form' => $form));
}
I am trying to add some validation to my entity, using the Symfony validation component, i have added some constraints to my User Entity.
/**
* #param ClassMetadata $metadata
*/
public static function loadValidatorMetadata(ClassMetadata $metadata)
{
$metadata->addPropertyConstraint('username', new Assert\NotBlank);
$metadata->addPropertyConstraint('password', new Assert\NotBlank);
$metadata->addPropertyConstraint('first_name', new Assert\NotBlank);
$metadata->addPropertyConstraint('last_name', new Assert\NotBlank);
}
Now i want to test if i get some errors, when violating the constraints, this is done like this.
$user = new User();
$user->username = '';
$user->password = '';
$validator = Validation::createValidator();
if (0 < count($validator->validate($user))) {
throw new \RuntimeException('The given user is invalid');
}
But the count is zero, which is odd, as all the constraints is clearly violated? Am i missing something here? Well i must be :D.
Might be worth to notice, that my application is not a Symfony application; it's a ordinary php application, i am just using the component.
You need to specify which methods act as mapping methods:
$validator = Validation::createValidatorBuilder()
->addMethodMapping('loadValidatorMetadata')
->getValidator()