Overposting a form is a common way of manipulating data / hacking a site.
The cause for that possible security problem is the Form/Model Binder, that automatically binds a Form to an object. Within ASP.NET, I know how to protect against these sort of attacks.
We have a User Model, with the following fields: ID, Firstname, Lastname, Password. I want the user to be able to change his Firstname and Lastname.
As we know, this happens within Symfony (and ASP.NET MVC) with a Form/Model Binder, that takes the "names" of the forms and maps these values to the corresponding object fields.
Solution in ASP.NET, using the [Bind] expression on each "Post-Controller":
public async Task<ActionResult> Create([Bind(Include="FirstName,Lastname")] Employee employee)
How can I prevent this kind of attack within a Symfony application? How to tell the Model/Form binder, which post data should only be accepted / expected?
#Edit:
This question intended to know how to solve this kind of problem when using a FormType for multiple usecases, e.g. for Creation and Edit of an employee. I know that in general, Symfony Form Component already checks if there are any additional fields.
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
if ($options['type'] === 'edit') {
$builder->add('editMe');
//More edit me fields
}
$builder->add('createMe');
//more create me fields
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setRequired(array(
'type'
));
$resolver->setDefaults(array(
'type' => 'create'
));
}
//For consistency
public function getName()
{
return 'foo';
}
}
There is no need for extra events since it would be an overkill.
Controller:
public function createFooAction(Request $request)
{
$form = $this->createForm(new FooType(), new Foo());
$form->handleRequest($request);
if ($form->isValid() && $form->submitted()) {
//flush form
}
return $this->render("AppBundle:Foo:create.html.twig", array(
'form' => $form
));
}
public function editFooAction(Request $request, $id)
{
$foo = ... //find($id)
$form = $this->createForm(new FooType(), $foo, array(
'type' => 'edit'
));
$form->handleRequest($request);
if ($form->isValid() && $form->submitted()) {
//flush form
}
return $this->render("AppBundle:Foo:edit.html.twig", array(
'form' => $form
));
}
Bonus
If you use the cookbook (read: normal) approach to symfony forms, you already have that protection, alongside with CSRF protecttion.
If you add an extra field to the request you will get the following error:
This form should not contain extra fields
Example Form:
class FooType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('title');
//Class Foo also has a text attribute
}
}
You will create an edit or create form using the above class in your controller and only the fields added to the builder can be modified. This way Foo::$text can not be modified using the form.
Related
So I have a form that is created in it's own class:
<?php
// src/AppBundle/Form/Type/CoffeeShopForm.php
namespace AppBundle\Form\Type;
use Symfony\Component\Form\AbstractType;
...
class CoffeeShopType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
->add('name', TextType::class)
...
->add('save', SubmitType::class, array('label' => 'Create a coffee shop'))
->getForm();
;
}
}
And then I am using it in one controller
/**
* #Route("/admin/edit/{coffee_shop_id}")
*/
public function editCoffeeShopAction(Request $request, $coffee_shop_id)
{
$repository = $this->getDoctrine()->getRepository('AppBundle:CoffeeShop');
$coffeeshop = $repository->find($coffee_shop_id);
$form = $this->createForm(CoffeeShopType::class, $coffeeshop);
$form->get('name')->setData('New name value');
$form->handleRequest($request);
return $this->render('AppBundle:CoffeeController:edit_coffee_shop.html.twig', array(
'form' => $form->createView(),
));
}
So in the controller as you may see I am able to change the value of the name field with $form->get('name')->setData('New name value');
My question is how may I change the label of the SubmitType field - I searched for the documentation of this thing but I can't find it, and it is really helpful if I could reuse this form since I am using it for the add form and then for the edit form and basically it is possible in Zend Framework, so I think it should be possible also in Symfony
I have stumbled across the same question, and after extensive debugging, I can say with some confidence that what you want to do is not possible, at least in Symfony 2.8 .
However, what you can do is setting another label when rendering the form:
{{ form_row(form.name, { 'label': 'Test 123'}) }}
See http://symfony.com/doc/current/reference/forms/types/text.html#label for details.
Hi stackexchange users,
I have a data (model) class which has two methods which look like this:
class ContactDetails {
public function setWebsite($address, $type) {
//do something...
}
public function getWebsite($type) {
//do something...
}
}
Now I want to create a form where the user can input a website address and choose a type (e.g. "private" or "business") for the address.
To make this possible I have created a custom form type like this
class ContactDetailsType extends AbstractType {
public function buildForm(FormBuilderInterface $builder, array $options) {
$builder
->add('type', 'text') //better: choice, but for the sake of demo...
->add('website', 'text')
;
}
public function getName() {
return 'ContactDetailsType';
}
public function setDefaultOptions(OptionsResolverInterface $resolver) {
$resolver
->setDefaults(array(
'data_class' => 'ContactDetails',
));
}
}
The controller then looks like this:
public function indexAction(Request $request) {
//generate completely new cost unit
$costunit = new ContactDetails();
//generate form
$form = $this->createForm(new ContactDetailsType(), $costunit);
$form->add('save', 'submit');
$form->handleRequest($request);
if ($form->isValid()) {
//yay!
}
}
This obviously doesn't work, as the form component doesn't know how to map these two fields from the type to the data model class.
Question: What is the best practise to map the data of two fields of a form to one method call in a data model class and vice-versa?
On your place a i would make both fields virtual in form and then use event listener to set data in entity.
Info about form events
I'm trying to build a Symfony form (in Silex) by name. Using the configuration below, I believe I should be able to call $form = $app['form.factory']->createBuilder('address');, however the FormRegistry cannot find a form of this type.
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Form\FormTypeExtensionInterface;
class AddressType extends AbstractType implements FormTypeExtensionInterface
{
public function getName()
{
return 'address';
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('addressee', 'text');
// .. fields ..
$builder->add('country', 'text');
}
public function getExtendedType()
{
return 'form';
}
}
This is then added to the form registry using the form.type.extensions provider:
$app['form.type.extensions'] = $app->share($app->extend('form.type.extensions', function($extensions) use ($app) {
$extensions[] = new AddressType();
return $extensions;
}));
Is there something else I need to do or a different way of building the form in this way?
Why not use direct
$app['form.factory']->createBuilder('Namespace\\Form\\Types\\Form')
First, sorry for my poor english. :)
I think you should extend form.extensions, instead of form.type.extensions.
Something like this:
$app['form.extensions'] = $app->share($app->extend('form.extensions',
function($extensions) use ($app) {
$extensions[] = new MyTypesExtension();
return $extensions;
}));
Then your class MyTypesExtension should look like this:
use Symfony\Component\Form\AbstractExtension;
class MyTypesExtension extends AbstractExtension
{
protected function loadTypes()
{
return array(
new AddressType(),
//Others custom types...
);
}
}
Now, you can retrieve your custom type this way:
$app['form.factory']->createBuilder('address')->getForm();
Enjoy it!
I see, this question is quite old but:
What you do is creating a new Form Type not extending an existing one, so the correct way to register it to add it to the 'form.types'. (Remember: form type extension is adding something to the existing types so for the future all instance will have that new 'feature'. Here you are creating a custom form type.)
$app['form.types'] = $app->share($app->extend('form.types', function ($types) use ($app) {
$types[] = new AddressType();
return $types;
}));
I think when you are coming from Symfony to Silex form.type.extension can be misleading.
From Symfony How to Create a Form Type Extension:
You want to add a generic feature to several types (such as adding a "help" text to every field type);
You want to add a specific feature to a single type (such as adding a "download" feature to the "file" field type).
So as your code shows you want to add a FormType which exists in Symfony but you would use the FormServiceProvider in Silex without defining an AbstractType and just use the form.factory service as shown in this example:
In your app.php:
use Silex\Provider\FormServiceProvider;
$app->register(new FormServiceProvider());
In your controller/action:
$form = $app['form.factory']->createBuilder('form', $data)
->add('name')
->add('email')
->add('gender', 'choice', array(
'choices' => array(1 => 'male', 2 => 'female'),
'expanded' => true,
))
->getForm()
;
I have a form with a choice type element. I need to populate it with data. As I know there are 3 methods.
1. Controller:
// Controller
public function myAction()
{
$choices = ...; // create choices array
$form = $this->createForm(new MyFormType($dm), null, array(
'choices' => $choices,
));
}
// Form
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'choice', array(
'choices' => $options['choices']
));
}
}
2. Form class + repository
// Controller
public function myAction()
{
$dm = $this->get('doctrine')->getManager();
$form = $this->createForm(new MyFormType($dm));
}
// Form
class MyFormType extends AbstractType
{
private $dm;
public function __construct($dm)
{
$this->dm = $dm;
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'choice', array(
'choices' => $options['choices']
));
}
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$list = array();
foreach($this->dm->getRepository('MyBundle:Cars')->findAll() as $car) {
$list[$car->getName()] = $car->getName();
}
$resolver->setDefaults(array(
'choices' => $list,
));
}
}
3. Form class + custom service
// Controller
public function myAction()
{
$dm = $this->get('doctrine')->getManager();
$form = $this->createForm(new MyFormType(), null, array(
'myservice' => $this->get('myservice'),
));
}
// Form
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'choice', array(
'choices' => $options['myservice']->getCars()
));
}
}
// Service
class MyService
{
const ENTITY_CAR = 'MyBundle:Cars';
/** #var DocumentManager */
private $dm;
public function __construct(DocumentManager $dm)
{
$this->dm = $dm;
}
public function getCars()
{
return $this->dm->getRepository("MyBundle:Cars")->findAll();
}
}
I'll express my thoughts.
The 1st option is not the best practice. Especially when complicated logic is involved. Controllers should be as tiny as possible.
The 2nd is much better. But it exposes entity name and problems may occur if I decide to rename it.
The 3rd is the best option, imho. Entity names are concentrated in one place, better IDE type hinting, centralized entity management (search, save, remove...). The main disadvantage is a possible over-engineered class as it's becoming responsible for many read/write operations. On the other hand it can be divided into pieces.
What do you think about it?
The third option is good if you have to reuse that service elsewhere in your code (and if that service will grown in comparison of that you've wrote, we'll see it later). In that way, as you said, "manager" of that entity is one and contains itself the name of repo,a const, and so on.
BUT
If this service is use only as a "pusher" for reach your repository by hiding its name, I don't think that this solution is still much good as it seems.
Obviously if that service is thought for have multiple persistance options and multiple retrieve option (base on what ORM you've selected), in that case this could be the best practice.
In other cases, I suppose that the second one is always the better.
The first isn't practicable unless you want to ignore all good practices
I suggest a fourth solution : use an entity field as it is designed to be a choice field with options loaded from DB !
Here is the official doc http://symfony.com/doc/master/reference/forms/types/entity.html
And how you may use it :
// Form
class MyFormType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('cars', 'entity', array(
'class' => 'MyBundle:Cars',
'property' => 'name',
//Optionnal if you need to condition the selection
'query_builder' => function(EntityRepository $er) {
return $er->createQueryBuilder('u')->orderBy('u.username', 'ASC');
},
));
}
}
I am using a Form class to help generate a form. It is a password reset form.
This form needs to be tied to my User entity, once the form is submitted the password will be updated.
Everything is working with the code I have currently, yet in an attempt to strip out some code that I believe shouldn't be required (due to using the data_class option) my validation breaks (as the form seems to become "detached" form the entity)
Ok, so some code:
public function passwordResetAction(Request $request, $key = NULL)
{
$user = $this->getDoctrine()
->getRepository('DemoUserBundle:User\User')
->findOneBy(array('resetToken' => $key));
if (!$user) {
throw $this->createNotFoundException('No user found for reset token '.$key.'!');
}
$form = $this->createForm(new PasswordResetType(), $user);
if ($request->getMethod() == 'POST') {
$form->bindRequest($request);
if ($form->isValid()) {
$postData = $request->request->get('resetpass');
$newPassword = $postData['password'];
$encoder = new MessageDigestPasswordEncoder('sha1', false, 1);
$password = $encoder->encodePassword($newPassword, $user->getSalt());
$user->setPassword($password);
$newToken = base64_encode($user->getUsername().'|'.date('Y-m-d'));
$user->setResetToken($newToken);
$em = $this->getDoctrine()->getEntityManager();
$em->persist($user);
$em->flush();
return $this->redirect($this->generateUrl('password_reset_success'));
}
}
return $this->render('DemoUserBundle:User\Reset:password-reset.html.twig', array('key' => $key, 'form' => $form->createView()));
}
The piece of code that I'm interested in is "$form = $this->createForm(new PasswordResetType(), $user);" As you can see, I'm using the Form Type to construct the form and passing it the $user variable that attaches the form to the User entity. Now, as far as I can understand, I should not need this parameter set as I have the entity set in the Form Type (see below for the code)
namespace Demo\UserBundle\Form\Type\User;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError;
use Symfony\Component\Form\CallbackValidator;
class PasswordResetType extends AbstractType
{
public function buildForm(FormBuilder $builder, array $options)
{
$builder->add('password', 'password', array('label' => 'New Password'));
$builder->add('confirmPassword', 'password', array('label' => 'Confirm Password', 'property_path' => false));
$builder->addValidator(new CallbackValidator(function($form)
{
if($form['confirmPassword']->getData() != $form['password']->getData()) {
$form['confirmPassword']->addError(new FormError('Passwords must match.'));
}
}));
}
public function getDefaultOptions(array $options)
{
return array(
'data_class' => 'Demo\UserBundle\Entity\User\User',
);
}
public function getName()
{
return 'resetpass';
}
}
So this form should be default attach to the User Entity (without the need for the $user variable) yet it doesn't work. Without that variable being passed, the validation simply doesn't work (an indicator that it isn't being attached to the user entity)
What gives? Any ideas folks?
This data_class option is used to set the appropriate data mapper to be used by the form. If you don't supply it, it guesses it based on the object you pass to it.
It doesn't free you from passing an object to the form. How do you think it would know which object you want to work with?
in the new symfony versions use another method signature (option resolver instead of $options variable):
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Demo\UserBundle\Entity\User\User',
));
}
Note for Symfony 2.7: the form API has slightly changed, so you should use this instead:
public function configureOptions(OptionsResolver $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'Demo\UserBundle\Entity\User\User',
));
}