repository with issue
I have a form for entity User with email field:
->add('email', EmailType::class, [
'constraints' => [
new NotBlank(),
new Email([
'checkMX' => true,
])
],
'required' => true
])
when i'm editing email to something like test#gmail.com1 and submit form it shows me error "This value is not a valid email address." THat's ok, but after that symfony populate wrong email into token and when i'm going to any other page or just reload page, i'm getting this:
WARNING security Username could not be found in the selected user
provider.
i think question is: why symfony populate wrong Email that failed validation into token and how i could prevent it?
controller:
public function meSettingsAction(Request $request)
{
$user = $this->getUser();
$userUnSubscribed = $this->getDoctrine()->getRepository('AppBundle:UserUnsubs')->findOneBy(
[
'email' => $user->getEmail(),
]
);
$form = $this->createForm(UserSettingsType::class, $user);
$form->get('subscribed')->setData(!(bool)$userUnSubscribed);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
/**
* #var $user User
*/
$user = $form->getData();
/** #var UploadedFile $avatar */
$avatar = $request->files->get('user_settings')['photo'];
$em = $this->getDoctrine()->getManager();
if ($avatar) {
$avatar_content = file_get_contents($avatar->getRealPath());
$avatarName = uniqid().'.jpg';
$oldAvatar = $user->getPhoto();
$user
->setState(User::PHOTO_STATE_UNCHECKED)
->setPhoto($avatarName);
$gearmanClient = $this->get('gearman.client');
$gearmanClient->doBackgroundDependsOnEnv(
'avatar_content_upload',
serialize(['content' => $avatar_content, 'avatarName' => $avatarName, 'oldAvatar' => $oldAvatar])
);
}
$subscribed = $form->get('subscribed')->getData();
if ((bool)$userUnSubscribed && $subscribed) {
$em->remove($userUnSubscribed);
} elseif (!(bool)$userUnSubscribed && !$subscribed) {
$userUnSubscribed = new UserUnsubs();
$userUnSubscribed->setEmail($form->get('email')->getData())->setTs(time());
$em->persist($userUnSubscribed);
}
$user->setLastTs(time());
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
$this->get('user.manager')->refresh($user);
return $this->redirectToRoute('me');
}
return $this->render(
':user:settings.html.twig',
[
'form' => $form->createView(),
]
);
}
UPD:
it works fine if i change in OAuthProvider:
/**
* #param \Symfony\Component\Security\Core\User\UserInterface $user
*
* #return \Symfony\Component\Security\Core\User\UserInterface
*/
public function refreshUser(UserInterface $user)
{
return $this->loadUserByUsername($user->getName());
}
to:
/**
* #param \Symfony\Component\Security\Core\User\UserInterface $user
*
* #return \Symfony\Component\Security\Core\User\UserInterface
*/
public function refreshUser(UserInterface $user)
{
return $this->userManager($user->getId());
}
but it seems to be dirty hack.
Thanks.
Your user token seems to be updated by the form, even if the email constraint stop the flush.
Can you check if your form past the isValid function ?
You can maybe try to avoid it with an event listener or a validator.
With an event SUBMIT you should be able to check the email integrity, and then add a FormError to avoid the refreshUser.
This is a tricky one, thanks to the repository it was easier to isolate the problem. You are binding the user object form the authentication token to the createForm() method. After the
$form->handleRequest($request)
call the email off the token user object is updated.
I first thought to solve this by implementing the EquatableInterface.html in the User entity but this did not work, as the compared object already had the wrong email address set.
It may also be useful to implement the EquatableInterface interface, which defines a method to check if the user is equal to the current user. This interface requires an isEqualTo() method.)
Than I thought about forcing a reload of the user from the db and resetting the security token, but in the it came to my mind, that it might be sufficient to just refresh the current user object from the database in case the form fails:
$this->get('doctrine')->getManager()->refresh($this->getUser());`
In your controller, this would solve your issue.
/**
* #Route("/edit_me", name="edit")
* #Security("has_role('ROLE_USER')")
*/
public function editMyselfAction(Request $request) {
$form = $this->createForm(User::class, $this->getUser());
if ($request->isMethod(Request::METHOD_POST)) {
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
$em = $this->getDoctrine()->getManager();
$em->persist($user);
$em->flush();
} else {
$this->get('doctrine')->getManager()->refresh($this->getUser());
}
}
return $this->render(':security:edit.html.twig',['form' => $form->createView()]);
}
Alternative solution
The issue at the Symfony repository resulted in some valuable input about Avoiding Entities in Forms and
Decoupling Your Security User which provides a more complex approach for a solution to your problem.
Related
i have this PATCH function but i need to add some form of authorization to ensure you can only edit/update a film that is associated with the current user, can i get some help on how to add this
controller function:
public function update(string $id)
{
$this->user = Auth::user();
$this->film = film:findOrFail($id);
return $this->film->toJson();
}
I've looked at the laravel docs at the validation section and seen this example
$validatedData = $request->validate([
'title' => 'required|unque:posts|max:255',
'body' => 'required',
]);
i then added my own validation at the top of the file
protected $validation = [
'name' => 'string',
'description' => 'new description'
];
im a little lost on how i implement authorization to ensure only a current user can update a film?
What you're looking for is not a form validation, but a User Authorization (as in the comments). So you should have a look at the official documentation. In your case you should write a FilmPolicy that may look like to this (I will skip the registration part... It can be easily understood from the docs):
class FilmPolicy {
/**
* Determine if the given film can be updated by the user.
*
* #param \App\User $user
* #param \App\Post $post
* #return bool
*/
public function update(User $user, Film $film)
{
return $user->id === $film->user_id; // Or whatever is your foreign key
}
}
Then you should update your controller in order to handle the authorization as follow:
public function update(string $id)
{
$this->film = film::findOrFail($id);
$this->authorize('update', $this->film);
return $this->film->toJson();
}
Since this method simply throws an exception, you can have a more elaborate response as explained in the docs
Ok basically, to enable what you need in a simple way, what you can do is this;
First pass the 'user_id' to the controller.
public function update(string $id, $userid)
{
$user = Auth::user();
$id = $user->id;
if($id == $userid)
{
$this->user = Auth::user();
$this->film = film::findOrFail($id);
return $this->film->toJson();
}else{
return "Not Authorized";
}
}
If im not misunderstanding your question, this basically allows only the user who is logged in to update his film. if he goes into any other profile, the id's would mismatch and thus return a not authorized prompt.
I executed the make:crud command for generate some files for the entity user.
All works like a charm, but I have one problem when I edit a user.
When I edit a user, I can :
change the password and save
or
don't modify the password and save
From the generated user 'edit' controller:
/**
* #Route("/{id}/edit", name="user_edit", methods={"GET","POST"})
*/
public function edit(Request $request, User $user): Response
{
$form = $this->createForm(UserType::class, $user);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
// HERE : How can I check if the password was changed ?
$this->getDoctrine()->getManager()->flush();
return $this->redirectToRoute('user_index', [
'id' => $user->getId(),
]);
}
return $this->render('user/edit.html.twig', [
'user' => $user,
'form' => $form->createView(),
'title' => 'edit userr'
]);
}
How can I check if the password was changed ? The user variable contains the new password value...
If the password is new, I must use the encoder service. If not, I just must update the user with new data
For this it helps to have a class variable in your User that is not written to the database. In your form you can then use this variable to temporarily store the updated password in, that you then encode and remove. This is what the eraseCredentials()-method in the UserInterface is for.
For example in your User you could have
class User implements UserInterface
{
private $plainPassword;
// ...
public function setPlainPassword(string $plainPassword)
{
$this->plainPassword = $plainPassword;
}
public function getPlainPassword()
{
return $this->plainPassword;
}
public function eraseCredentials()
{
$this->plainPassword = null;
}
}
Notice how private $plainPassword does not have any ORM-annotations, that means it will not be stored in the database. You can however use validation constraints, e.g. if you want to make sure that passwords have a minimum length or a certain complexity. You will still require the original password field that stores the encrypted password.
You then add this field to your user update-form instead of the actual password field. In your controller you can then only check if the new plainPassword-field was filled out and then read the value, encode it and replace the actual password field.
if ($form->isSubmitted() && $form->isValid()) {
$user = $form->getData();
if ($user->getPlainPassword() !== null) {
$user->setPassword($this->userPasswordEncoder->encode(
$user->getPlainPassword(),
$user
);
}
// ...
Another way of doing this, without adding this "helper"-property to the user is using an unmapped form field:
# UserForm
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder
// ...
->add('plainPassword', PasswordType::class, ['mapped' => false])
;
}
The controller will look similar, only you fetch the data from the unmapped field instead of from the user:
$form->get('plainPassword')->getData();
I have the following controller method which creates a new Category entity and persists it to the database:
/**
* #param Request $request
*
* #return array
*
* #Route("/admin/category/new", name="_admin_category_new")
* #Method({"GET", "POST"})
* #Template("Admin/category_new.html.twig")
*/
public function newCategoryAction(Request $request)
{
$category = new Category();
$form = $this->createForm(CategoryType::class, $category);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($category);
if (!$category->getSlug()) {
$category->setSlug();
}
if ($category->getFile() != null) {
$um = $this->get('stof_doctrine_extensions.uploadable.manager');
$um->markEntityToUpload($category, $category->getFile());
}
$em->flush();
$this->addFlash('success', 'Category successfully created');
$this->redirect($this->generateUrl('_admin_category', array('page' => 1)));
}
return array('form' => $form->createView());
}
Upon successful completion, it's supposed to redirect the user to a different URL. Instead, it just re-displays the current page/form. Any ideas? The route _admin_category does exist, and it is working:
$ bin/console debug:router
...
_admin_category GET ANY ANY /admin/category/{page}
...
And my Category entities are being saved to the DB properly.
You should return redirect response try
return $this->redirectToRoute('_admin_category', ['page' => 1]);
Redirect method creates an object of RedirectResponse class, and it needs to be returned as response. Moreover, you don't have to use redirect + generateUrl you can just use redirectToRoute method which is shortcut for that.
Also I'd suggest wrapping flush with try/catch
For more see docs
I'm using Symfony2 with FOSUserBundle, the problem I have encountered was in editing a user account, when I tried to edit a specific user account, the retrieved user info is correct but when I tried to update the retrieved user info, the currently logged account would be the one being edited not the retrieved user account, how is it possible? What's wrong with my code?
ProfileController :
//edit user
public function editUserAction($id, Request $request)
{
$em = $this->getDoctrine()->getManager();
$user = $em->getRepository('MatrixUserBundle:User')->find($id);
if (!is_object($user)) {
throw new AccessDeniedException('This user does not have access to this section.');
}
/** #var $formFactory \FOS\UserBundle\Form\Factory\FactoryInterface */
$formFactory = $this->get('fos_user.profile.form.factory');
$form = $formFactory->createForm();
$form->setData($user);
$form->handleRequest($request);
if ($form->isValid()) {
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->get('fos_user.user_manager');
$userManager->updateUser($user);
$session = $this->getRequest()->getSession();
$session->getFlashBag()->add('message', 'Successfully updated');
$url = $this->generateUrl('matrix_edi_viewUser');
$response = new RedirectResponse($url);
}
return $this->render('FOSUserBundle:Profile:edit.html.twig', array(
'form' => $form->createView()
));
}
After the given user is correctly updated, you are returning a redirection to a custom route.
I guess the route matrix_edi_viewUser is an override of the original FOSUB\ProfileController::showAction.
Also, you have to create a specific showUserAction and pass it the updated user as argument.
The following code should works :
public function showUserAction($id)
{
$user = $em->getDoctrine()
->getManager()
->getRepository('MatrixUserBundle:User')
->find($id);
if (!is_object($user)) {
throw new AccessDeniedException('This user does not have access to this section.');
}
return $this->render('FOSUserBundle:Profile:show.html.twig', array('user' => $user));
}
The route :
matrix_edi_viewUser:
path: /users/{id}/show
defaults: { _controller: MatrixUserBundle:Profile:showUser }
And in your editAction, change your redirection to :
$url = $this->generateUrl('matrix_edi_viewUser', array('id' => $user->getId());
$response = new RedirectResponse($url);
Now, the user informations displayed in show will be the informations of the updated user.
I have an issue where I have a form type that persists the associated entity even when the form is not valid.
I have confirmed that the form indeed has errors via $form->getErrorsAsString(). I have also confirmed that the logical if statement that checks if the form is valid or not comes out false. The entity still persists despite the fact that the form is never valid.
I'm not sure what I'm doing wrong here as I have no other spot that I can find that either persists the entity or flushes the entity manager. Here's my controller:
/**
* #Route("/settings/profile", name="settings_profile")
* #Template();
*/
public function profileAction()
{
$user = $this->getUser();
$profile = $user->getUserProfile();
if (null === $profile) {
$profile = new UserProfile();
$profile->setUser($user);
$profileDataModel = $profile;
} else {
$profileDataModel = $this->getDoctrine()->getManager()->find('MyAppBundle:UserProfile',$profile->getId());
}
$form = $this->createForm(new ProfileType(),$profileDataModel);
$request = $this->getRequest();
if ($request->getMethod() === 'POST') {
$form->bind($request);
if ($form->isValid()) {
// This logic never gets executed!
$em = $this->getDoctrine()->getManager();
$profile = $form->getData();
$em->persist($profile);
$em->flush();
$this->get('session')->setFlash('profile_saved', 'Your profile was saved.');
return $this->redirect($this->generateUrl('settings_profile'));
}
}
return array(
'form' => $form->createView(),
);
}
I must have a listener or something somewhere that is persisting the user.
My work around for this temporarily is to do:
$em = $this->getDoctrine()->getManager()
if ($form->isValid()) {
// persist
} else {
$em->clear();
}
Until I can ferret out what listener or other data transformer is causing this.