How to remove Form Field From FOSUserBundle Symfony2.8? - symfony

I have install FOSUserBundle in my Symfony project. Now I want to Remove Registration Form Field that by default provide by FOSUserBundle.
Registration Form Fields Are :
User Name
Email Id
Password
Repeat Password
Now I don't want Email Field when User are register so I override Registration form in my bundle.
\\ Front\FrontBundle\Form\RegistrationType.php
<?php
namespace Front\FrontBundle\Form;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationType extends AbstractType
{
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->remove('email'); // here I code for remove email field.
}
public function getParent()
{
return 'FOS\UserBundle\Form\Type\RegistrationFormType';
// Or for Symfony < 2.8
// return 'fos_user_registration';
}
public function getBlockPrefix()
{
return 'app_user_registration';
}
// For Symfony 2.x
public function getName()
{
return $this->getBlockPrefix();
}
}
then I change config.yml and services.yml file
\\ App/config/config.yml
fos_user:
db_driver: orm
firewall_name: main
user_class: Front\FrontBundle\Entity\User
registration:
form:
type: Front\FrontBundle\Form\RegistrationType
\\app/config/services.yml
services:
app.form.registration:
class: Front\FrontBundle\Form\RegistrationType
tags:
- { name: form.type, alias: app_user_registration }
So After done with this Email Field remove from my Registration Form but when I submit form after filling username , password , repeat password it's give me any error that The email is not valid.
So I need to change any other file to remove email validation with email field ?
Thanks.

you can remove the field as you have done, but you must make sure that there is no residual server side validation that requires it.
how to do this depends on how you've extended the FOS User class (or if you have).
annotated constraints look like this for instance (from the docs)
class Author
{
/**
* #Assert\NotBlank() <--- remove this
*/
public $name;
}
if youv'e extended the class, you can remove it from your own member definition.
If you've not extended it, extend it and then dont put in the validation constraint.
Or failing everything else (and I think this is hacky), issue a default in the controller before you call isValid().
public function someAction(Request $request) {
// ...
$user = new User();
// fill empty value
$user->setEmail('blank#blank.blank');
// form stuff here
// ...
if ($form->isValid()) {
// do some stuff
}
return $this->render(blahblahbal);
}

Related

How to get dynamic security privileges with Symfony Security

I'm need to do add custom access law to my access control on Symfony, I try to explain me.
I have a web application with some customer, and I want to able the access of some part of the code when that customer have the right plugin. So this is my customer:
namespace AppBundle\Entity;
class CustomerProfile{
private $id;
private $user;
private $plugins;
}
The Entity for the Plugin
namespace AppBundle\Entity;
class Plugin{
private $id;
private $name;
private $customerProfiles;
}
I use doctrine for the relations so from customer i can get his plugins. For example, we have 2 customer and 2 plugin:
AppBundle\Entity\CustomerProfile:
customer_1:
user: '#user_1'
plugins: ['#plugin_1','#plugin_2']
customer_2:
user: '#user_2'
plugins: ['#plugin_1']
AppBundle\Entity\Plugin:
plugin_1:
name: 'plugin 1'
plugin_2:
name: 'plugin 2'
In my project all the code about customer is under the /customer namespace, Symfony like, and all work.
access_control:
- { path: ^/customer, roles: ROLE_CUSTOMER }
But, for this customer with different plugin i would set an dinamic access control, but i don't know how. I need to control something like this:
access_control:
- { path: ^/code_for_plugin_1, roles: ROLE_CUSTOMER_WHIT_PLUGIN_1}
- { path: ^/code_for_plugin_2, roles: ROLE_CUSTOMER_WHIT_PLUGIN_2}
but I think the good way is set a "sub role" (if exist) to set for each customer that have a plugin a role to access in that namespace.
I hope I was clear enough, thanks for help.
I would suggest to use custom voter rather than the access_control and role approach as
this is in my opinion more flexible for your use case. Your suggested solution requires
a generated role (ROLE_CUSTOMER_WHIT_PLUGIN_{X}) for every plugin, which in case
of adding pluggins dynamically would just not work.
Check the How to Use Voters to Check User Permissions article in Symfony documentation for more detail.
You basically need to implement a user voter which will check if the logged user has an access
to requested resource. In your case it would look similar to this:
/src/YourBundle/Controller/YourController.php
<?php
...
class YourController extends Controller
{
public function getFooAction($id)
{
$this->denyAccessUnlessGranted(YourVoter::VIEW_FOO);
// ...method logic
}
public function getBarAction($id)
{
$this->denyAccessUnlessGranted(YourVoter::VIEW_BAR);
// ...method logic
}
}
/src/YourBundle/Security/YourVoter.php
<?php
...
class YourVoter extends AbstractVoter
{
const VIEW_FOO = 'YOUR_VIEW_FOO';
const VIEW_BAR = 'YOUR_VIEW_BAR';
public function getVoterAttributes()
{
return [self::VIEW_FOO, self::VIEW_BAR,];
}
protected function supports($attribute, $subject)
{
...
}
protected function voteOnAttribute($attribute, $item, TokenInterface $token)
{
$user = $token->getUser();
if (!$user instanceof User) {
return false;
}
switch ($attribute) {
case self::VIEW_FOO:
return $this->canViewFoo($user);
case self::VIEW_BAR:
return $this->canViewBar($user);
}
throw new \Exception(sprintf(
'Invalid vote attribute "%s".',
$attribute
));
}
private function canViewFoo(User $user)
{
return $user->getProfile()->hasRoleFooXYZ()
}
private function canViewBar(User $user)
{
return $user->getProfile()->hasRoleBarXYZ()
}
}

How to impersonate user by id instead of username in symfony?

I can't figure out how to impersonate a user by user's id instead of user's username in Symfony?
The following trick which works with username can't work with id, as symfony is looking for username:
?_switch_user={id}
This is impossible to do without implementing your own firewall listener, as behind the scenes it loads the user from the userprovider (which only has a loadUserByUsername() method in its interface).
You could however implement your own firewall listener and get inspired by having a look at the code in Symfony\Component\Security\Http\Firewall\SwitchUserListener. For detailed information on implementing your own authentication provider, check the cookbook article.
EDIT:
One possible solution might be registering an extra request listener:
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
class LookupSwitchUserListener implements EventSubscriberInterface
{
private $repository;
public function __construct(UserRepository $repository)
{
$this->repository = $repository;
}
public static function getSubscribedEvents()
{
return [
KernelEvents::REQUEST => ['lookup', 12] // before the firewall
];
}
public function lookup(GetResponseEvent $event)
{
$request = $event->getRequest();
if ($request->has('_switch_user') {
return; // do nothing if already a _switch_user param present
}
if (!$id = $request->query->has('_switch_user_by_id')) {
return; // do nothing if no _switch_user_by_id param
}
// lookup $username by $id using the repository here
$request->attributes->set('_switch_user', $username);
}
}
Now register this listener in the service container:
services:
my_listener:
class: LookupSwitchUserListener
tags:
- { name: kernel.event_subscriber }
Calling a url with the ?_switch_user_by_id=xxx parameter should now correctly look up the username and set it so the SwitchUserListener can switch to the specified user.

Symfony2 FOSUserBundle relates to another Entity automatically

I recently implemented the FOSUserBundle in my website as the login procedure. I want it to expand the Author class. So whenever a new user is registered via the FOSUSerBundle a new entry is created in the Author class. Inside the Author class I set slug, createdAt and other useful parameters. The field I want to pass to Authot entity from FOSUserBundle is the "Name" field. Then I want to cascade the FOSUser entity and if its deleted, delete also the Author entity.
So schematically FOSUserBundle.username => Author.name
I do not know how to implement this code except that it has a #ORM/OneToOne relationship. Any ideas please?
You'll have to insert the Author manually after your user registration is completed. The FOSUserBundle provides a way to hook into events like post registration completion. You can create a listener to the FOSUserEvents::REGISTRATION_COMPLETED event and create your Author entity there.
See documentation here: https://github.com/FriendsOfSymfony/FOSUserBundle/blob/master/Resources/doc/controller_events.md
For example:
services.yml:
services:
my_user_registration_service:
class: MyBundle\EventListener\MyUserRegistrationListener
arguments: [#doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber }
MyUserRegistrationListener:
namespace MyBundle\EventListener;
use Doctrine\ORM\EntityManager;
use FOS\UserBundle\Event\FormEvent;
use FOS\UserBundle\FOSUserEvents;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use MyBundle\Entity\Author;
class EventSubscriber implements EventSubscriberInterface
{
private $em;
public function __construct(EntityManager $em)
{
$this->em = $em;
}
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_COMPLETED => 'addAuthor',
);
}
public function addAuthor(FilterUserResponseEvent $event)
{
$user = $event->getUser();
$author = new Author();
$author->setName($user->getUsername();
$this->em->persist($author);
$this->em->flush();
}
}

How to assign roles on successful registration?

I'm using fos user bundle and pugx multi user bundle.
I've read all the documentation and I'm new to Symfony.
In the pugx multi user bundle there's a sample on every point but one: sucessful registration.
Samples of overriding controllers for generating forms => ok
Samples of overriding templates for generating forms => ok
Samples of overriding successful registration sample => nothing.
Here's my code:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
$response = parent::registerAction($request);
return $response;
}
public function registerTeacherAction()
{
return $this->container
->get('pugx_multi_user.registration_manager')
->register('MyBundle\Entity\PersonTeacher');
}
public function registerStudentAction()
{
return $this->container
->get('pugx_multi_user.registration_manager')
->register('MyBundle\Entity\PersonStudent');
}
}
The problem is with ->get('pugx_multi_user.registration_manager') which returns a manager. In the fos user overring controllers help, they get either a form or a form.handler. I'm having hard times to "link" those with the pugx_multi_user manager.
What code should I put in the registerTeacherAction() to set roles for teacher, and in registerStudentAction() to set roles for student on a successful registration?
Solution 1 (Doctrine Listener/Subscriber)
You can easily add a doctrine prePersist listener/subscriber that adds the roles/groups to your entities depending on their type before persisting.
The listener
namespace Acme\YourBundle\EventListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Acme\YourBundle\Entity\Student;
class RoleListener
{
public function prePersist(LifecycleEventArgs $args)
{
$entity = $args->getEntity();
$entityManager = $args->getEntityManager();
// check for students, teachers, whatever ...
if ($entity instanceof Student) {
$entity->addRole('ROLE_WHATEVER');
// or
$entity->addGroup('students');
// ...
}
// ...
}
}
The service configuration
# app/config/config.yml or load inside a bundle extension
services:
your.role_listener:
class: Acme\YourBundle\EventListener\RoleListener
tags:
- { name: doctrine.event_listener, event: prePersist }
Solution 2 (Doctrine LifeCycle Callbacks):
Using lifecycle callbacks you can integrate the role-/group-operations directly into your entity.
/**
* #ORM\Entity()
* #ORM\HasLifecycleCallbacks()
*/
class Student
{
/**
* #ORM\PrePersist
*/
public function setCreatedAtValue()
{
$this->addRole('ROLE_WHATEVER');
$this->addGroup('students');
}
Solution 3 (Event Dispatcher):
Register an event listener/subscriber for the "fos_user.registration.success" event.
How to create an event listener / The EventDispatcher component.

Set the email manually with FOSUserBundle

I am using Symfony 2 + FOSUserBundle to manage my users.
The problem is that I would like to build the email address "manually" from the username, and so, do not require any field for the email.
I will create the email from the username because one requirement to sign up is to have an email from my university which follows a strict format (username#schoolname.fr).
I managed to override the RegistrationFormType to avoid the email field from being added to my sign up page but I still have the error "Please enter an email" when I submit the form.
How can I prevent the validation of the email address and how could I "build" it from the username?
Thanks!
(Sorry for the English, I know it's not perfect...)
There is a simple way around it. It is even mentioned in the official FOSUserBundle documentation. You just need to override the controller.
Create your own Bundle and extend the FOSUserBundle:
class CustomUserBundle extends Bundle
{
public function getParent()
{
return 'FOSUserBundle';
}
}
And then override the RegistrationController:
class RegistrationController extends BaseController
{
public function registerAction(Request $request)
{
// here you can implement your own logic. something like this:
$user = new User();
$form = $this->container->get('form.factory')->create(new RegistrationType(), $user);
if ($request->getMethod() == 'POST') {
$form->bind($request);
if ($form->isValid()) {
$user->setEmail($user->getUsername() . '#' . $user->getSchoolname() . '.fr');
// and what not. Also don't forget to either activate the user or send an activation email
}
}
}
}
You should write event listener for fos_user.registration.initialize. From code docs:
/**
* The REGISTRATION_INITIALIZE event occurs when the registration process is initialized.
*
* This event allows you to modify the default values of the user before binding the form.
* The event listener method receives a FOS\UserBundle\Event\UserEvent instance.
*/
const REGISTRATION_INITIALIZE = 'fos_user.registration.initialize';
More info about event dispatcher: http://symfony.com/doc/current/components/event_dispatcher/introduction.html
And example event listener: http://symfony.com/doc/current/cookbook/service_container/event_listener.html
UPDATE - how to code?
In your config.yml (or services.yml or other extension like xml, php) define service like this:
demo_bundle.listener.user_registration:
class: Acme\DemoBundle\EventListener\Registration
tags:
- { name: kernel.event_listener, event: fos_user.registration.initialize, method: overrideUserEmail }
Next, define listener class:
namespace Acme\DemoBundle\EventListener;
class Registration
{
protected function overrideUserEmail(UserEvent $args)
{
$request = $args->getRequest();
$formFields = $request->get('fos_user_registration_form');
// here you can define specific email, ex:
$email = $formFields['username'] . '#sth.com';
$formFields['email'] = $email;
$request->request->set('fos_user_registration_form', $formFields);
}
}
Notice: Of course you can validate this email via injecting #validator to the listener.
Now you should hide email field in registration form. YOu can do that by overriden register_content.html.twig or (in my oppinion better way) override FOS RegistrationFormType like this:
namespace Acme\DemoBundle\Form\Type;
use FOS\UserBundle\Form\Type\RegistrationFormType as BaseType;
use Symfony\Component\Form\FormBuilderInterface;
class RegistrationFormType extends BaseType
{
// some code like __construct(), getName() etc.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// some code for your form builder
->add('email', 'hidden', array('label' => 'form.email', 'translation_domain' => 'FOSUserBundle'))
;
}
}
Now your application is ready for setting email manually.

Resources