Create a new OneToOne relation on the registration - php

I am new with Symfony and FOSUserBundle, I have a relation One To One between User and Stream, when a user want to register he check if he is a streamer or not. If he is a streamer I want to create a new stream in relation with the user so here is the registerAction from FOSUserBundle that I'm overriding :
$form = $this->container->get('fos_user.registration.form');
$formHandler = $this->container->get('fos_user.registration.form.handler');
$confirmationEnabled = $this->container->getParameter('fos_user.registration.confirmation.enabled');
$process = $formHandler->process($confirmationEnabled);
if ($process) {
$user = $form->getData();
/*This is what I added*/
if($user->getIsStreamer()){
$em = $this->getDoctrine()->getManager();
$stream = new Stream();
$stream->setUser($user);
$em->persist($stream);
$em->flush();
}
/********/
$authUser = false;
if ($confirmationEnabled) {
$this->container->get('session')->set('fos_user_send_confirmation_email/email', $user->getEmail());
$route = 'fos_user_registration_check_email';
} else {
$authUser = true;
$route = 'fos_user_registration_confirmed';
}
$this->setFlash('fos_user_success', 'registration.flash.user_created');
$url = $this->container->get('router')->generate($route);
$response = new RedirectResponse($url);
if ($authUser) {
$this->authenticateUser($user, $response);
}
return $response;
}
But nothing happened, what did I missed ?

You are missing the part that add the Stream to the user (and not only set the user of the stream).
First, be sure your association contains cascade={"persist"} to avoid need of manually persist the Stream :
/**
* #ORM\OneToOne(targetEntity="Stream", cascade={"persist"},
*/
protected $stream;
Then, change your setStream to make it calling $stream->setUser automatically :
public function setStream(Stream $stream = null)
{
$this->stream = $stream;
$stream->setUser($this); // Setter calling
return $this;
}
Now you can replace the logic of your condition like this :
if($user->getIsStreamer()){
$stream = new Stream();
$user->setStream($stream);
}
The association should be correctly stored when calling $em->flush at the end of your method.
In the case of FOSUB registration it should be :
$this->get('fos_user.user_manager')->updateUser($user);.
It's done in the confirmAction where your user is redirected in success.

Related

Symfony 4 Validator - Auto logout if invalid

I wanted to validate my User Object ($user) with the Symfony Validator and return a JsonResponse ($response) if the form input data is valid / is not valid.
But I have the issue that I get logged out automatically when the data could not be validated. I have to login again and this is not the behaviour I expect when some data ist not valid. I found a workaround (see comments below) but this is not very satisfying :/
Here is the method of my Controller:
/**
* Update user profile data
*
* #Route("/api/users/updateprofile")
* #Security("is_granted('USERS_LIST')")
*/
public function apiProfileUpdate(ValidatorInterface $validator, FlashMessageBuilder $flashMessageBuilder)
{
$request = Request::createFromGlobals();
// Prepare Response
$response = new JsonResponse();
$response->setData([]);
/** #var User $user */
$user = $this->getUser();
$oldName = $user->getName();
$oldEmail = $user->getEmail();
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$errors = $validator->validate($user);
if (count($errors) > 0) { // if this -> auto logout
$user->setName($oldName); // if I set the both attributes back to the old value
$user->setEmail($oldEmail); // then I don't get logged out automatically but this is just a workaround and not satisfying
$entityManager = $this->getDoctrine()->getManager(); // forgot to remove this
$entityManager->persist($user); // and this line, this is actually deleted in the real code
foreach ($errors as $error) {
$errorMessage = $error->getMessage();
$errorField = $error->getPropertyPath();
$flashMessageBuilder->addErrorMessage($errorMessage, $errorField);
};
return $response;
}
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
$flashMessageBuilder->addSuccessMessage("Success!");
return $response;
}
Sorry for my bad english and thank you in advance!
You are persisting the User Object even if there are errors in validation, which can cause the problem with the logout.
Try to update ONLY in case there is no validation error:
public function apiProfileUpdate(ValidatorInterface $validator, FlashMessageBuilder $flashMessageBuilder)
{
$request = Request::createFromGlobals();
// Prepare Response
$response = new JsonResponse();
$response->setData([]);
/** #var User $user */
$user = $this->getUser();
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$errors = $validator->validate($user);
if (count($errors) == 0) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
$flashMessageBuilder->addSuccessMessage("Success!");
return $response;
}
foreach ($errors as $error) {
$errorMessage = $error->getMessage();
$errorField = $error->getPropertyPath();
$flashMessageBuilder->addErrorMessage($errorMessage, $errorField);
};
return $response;
}
But I think you should not mix API calls and the classical form approach utilizing FlashMessages, instead return a proper JSON result.
So consider to change the code accordingly:
public function apiProfileUpdate(ValidatorInterface $validator, FlashMessageBuilder $flashMessageBuilder)
{
$request = Request::createFromGlobals();
/** #var User $user */
$user = $this->getUser();
$user->setName($request->request->get('name'));
$user->setEmail($request->request->get('email'));
$errors = $validator->validate($user);
if (count($errors) == 0) {
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($user);
$entityManager->flush();
return new JsonResponse(['success' => true]);
}
$data = [];
foreach ($errors as $error) {
$data[$error->getPropertyPath()] = $error->getMessage();
};
return new JsonResponse(['success' => false, 'errors' => $data], 400);
}
Now your calling code can handle a 200 result (success) and the error case with status code 400 and display the error messages for all failed fields from the errors part in the result body.

How save access_token to db using yii2-dektrium facebook login?

I'm using yii2-dektrium to allow users login with their facebook's accounts.
After the login is done, I need to make API request from my server to get data of the user's accounts. One example of request is:
$client = Yii::$app->authClientCollection->getClient('facebook');
$response = $client->createApiRequest()
->setMethod('GET')
->setUrl('v2.12/me/accounts')
->send();
The access_token is saved on session so I need to persist it to the database.
I already added a column access_token to the social_account default table of yii2-dektrium but I don't know how to get and save it, and further more, how to apply it to the requests.
After reading for a while. I think the way to save it is overriding the method connect in dektrium\user\controllers\SecurityController.
public function connect(ClientInterface $client)
{
/** #var Account $account */
$account = \Yii::createObject(Account::className());
$event = $this->getAuthEvent($account, $client);
$this->trigger(self::EVENT_BEFORE_CONNECT, $event);
$account->connectWithUser($client);
$this->trigger(self::EVENT_AFTER_CONNECT, $event);
$this->action->successUrl = Url::to(['/user/settings/networks']);
}
And for applying to the request, override applyAccessTokenToRequest on yii\authclient\clients\Facebook
public function applyAccessTokenToRequest($request, $accessToken)
{
parent::applyAccessTokenToRequest($request, $accessToken);
$data = $request->getData();
if (($machineId = $accessToken->getParam('machine_id')) !== null) {
$data['machine_id'] = $machineId;
}
$data['appsecret_proof'] = hash_hmac('sha256', $accessToken->getToken(), $this->clientSecret);
$request->setData($data);
}
I can't get it done. And I'm not sure if it is the right way to do it. What I'm missing?
For save the access_token the first time you have to overwrite the connect action from \dektrium\user\controllers\SecurityController.
class SecurityController extends \dektrium\user\controllers\SecurityController
{
public function connect(ClientInterface $client)
{
// default implementation of connect
$account = \Yii::createObject(Account::className());
$event = $this->getAuthEvent($account, $client);
$this->trigger(self::EVENT_BEFORE_CONNECT, $event);
$account->connectWithUser($client);
$this->trigger(self::EVENT_AFTER_CONNECT, $event);
// get acess_token from $client
$access_token['tokenParamKey'] = $client->getAccessToken()->tokenParamKey;
$access_token['tokenSecretParamKey'] = $client->getAccessToken()->tokenSecretParamKey;
$access_token['createTimestamp'] = $client->getAccessToken()->createTimestamp;
$access_token['_expireDurationParamKey'] = $client->getAccessToken()->getExpireDurationParamKey();
$access_token['_params'] = $client->getAccessToken()->getParams();
// save acess_token to social_account table
$model = SocialAccount::find()->where(['provider' => $client->getName()])->andWhere(['user_id' => Yii::$app->user->id])->one();
$model->access_token = \yii\helpers\Json::encode($access_token);
$model->save(false);
$this->action->successUrl = Url::to(['/user/settings/networks']);
}
}
To get the access_token store in the database for further API Requests create a class that extends yii\authclient\SessionStateStorage and overwrite get method.
namespace app\models\authclient;
class DbStateStorage extends SessionStateStorage
{
public function get($key)
{
// $key is a complex string that ends with 'token' if the value to get is the actual access_token
$part = explode('_', $key);
if (count($part) == 3 && $part[2] == 'token') {
$account = SocialAccount::find()
->where(['provider' => $part[1]])
->andWhere(['user_id' => Yii::$app->user->id])
->one();
if ($account != null) {
$access_token = json_decode($account->access_token);
$token = new \yii\authclient\OAuthToken();
$token->createTimestamp = $access_token->createTimestamp;
$token->tokenParamKey = $access_token->tokenParamKey;
$token->tokenSecretParamKey = $access_token->tokenSecretParamKey;
$token->setParams((array)$access_token->_params);
$token->setExpireDurationParamKey($access_token->_expireDurationParamKey);
return $token;
}
}
if ($this->session !== null) {
return $this->session->get($key);
}
return null;
}
}
Finally set the DbStateStorage to your authclient
class Facebook extends \dektrium\user\clients\Facebook
{
public function __construct()
{
$this->setStateStorage('app\models\authclient\DbStateStorage');
}
}

REGISTRATION_SUCCESS event isn't triggered on FOSUserBundle

I need a little help here since my code doesn't work and I can't find where I'm failing. See this is the function I'm using to register users on my application (taked from here and changed a bit to suite my needs):
public function registerAction(Request $request)
{
$em = $this->getDoctrine()->getManager();
/** #var $userManager \FOS\UserBundle\Model\UserManagerInterface */
$userManager = $this->container->get('fos_user.user_manager');
/** #var $dispatcher \Symfony\Component\EventDispatcher\EventDispatcherInterface */
$dispatcher = $this->container->get('event_dispatcher');
$user = $userManager->createUser();
$event = new GetResponseUserEvent($user, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_INITIALIZE, $event);
if (null !== $event->getResponse()) {
return $event->getResponse();
}
$entity = new SysUsuario();
$form = $this->createForm(new UsuarioType(), $entity);
$form->handleRequest($request);
$user_data = $request->get('user_register');
$profile_data = $request->get('user_register')['perfil'];
if ($form->isValid()) {
$user->setUsername($profile_data['persJuridica'] . $profile_data['rif']);
$user->setEmail($user_data['email']);
$user->setPlainPassword($user_data['password']);
$role = $profile_data['roleType'];
if ($role === "O") {
$user->addRole("ROLE_OPERADOR");
}
elseif ($role === "CH") {
$user->addRole("ROLE_CENTRO_HIPICO");
}
$userManager->updateUser($user);
$profile = new SysPerfil();
$profile->setPersJuridica($profile_data['persJuridica']);
$profile->setRif($profile_data['rif']);
$ci = isset($profile_data['ci']) ? $profile_data['ci'] : null;
if ($ci != NULL) {
$profile->setCi($profile_data['ci']);
}
$profile->setNombre($profile_data['nombre']);
$profile->setApellido($profile_data['apellido']);
$profile->setRoleType($profile_data['roleType']);
$profile->setUser($user);
$em->persist($profile);
$em->flush();
$event = new FormEvent($form, $request);
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_SUCCESS, $event);
if (null === $response = $event->getResponse()) {
$url = $this->container->get('router')->generate('fos_user_registration_confirmed');
$response = new RedirectResponse($url);
}
$dispatcher->dispatch(FOSUserEvents::REGISTRATION_COMPLETED, new FilterUserResponseEvent($user, $request, $response));
return $response;
}
else {
$errors = $this->getFormErrors($formProfile);
}
return new JsonResponse(array('status' => true, 'errors' => $errors));
}
The problem here is that my code is never triggering the REGISTRATION_SUCCESS event so I didn't get any email and therefore I can't confirm users, what I'm doing wrong?
After dig and dig deeper in my code I found where the problem was. Thanks to #tttony user who turns on the bulb, I, accidentally, delete the confirmation configuration at config.yml and for that reason the event never was triggered, so the easy solution:
fos_user:
....
registration:
confirmation:
from_email:
address: admin#local.com
sender_name: Myself
Now before found the previous solution I come with another one, which made me study and learn and I leave here too:
/** Disable the user by default - this is done in the event */
$user->setEnabled(false);
/** #var $mailer FOS\UserBundle\Mailer\MailerInterface */
$mailer = $this->container->get('fos_user.mailer');
if (null === $user->getConfirmationToken()) {
$user->setConfirmationToken($str->generateRandomString(32));
}
$mailer->sendConfirmationEmailMessage($user);
$session = new Session();
$session->set('fos_user_send_confirmation_email/email', $user->getEmail());
The only thing I wont be able to do here was use the FOS\UserBundle\Util\TokenGeneratorInterface I don't know how to, so if any knows I'll be grateful if leave the answer to that one, both solutions works since I tested.

refresh security.context in symfony2

I have the following app flow:
Add role action:
public function addroleAction($role) {
$securityContext = $this->get('security.context');
$em = $this->getDoctrine()->getManager();
$user = $this->get('security.context')->getToken()->getUser();
$userId = $user->getId();
$userObj = $em->getRepository('ProjectEntityBundle:User')->find($userId);
switch ($role) {
case 6://student
$userObj->addRole('ROLE_STUDENT');
$em->flush();
$securityContext->getToken()->setUser($userObj);
break;
}
return $this->redirect($this->generateUrl('check_role'));
}
Check role action:
public function checkroleAction() {
$securityContext = $this->get('security.context');
if ($securityContext->isGranted('ROLE_STUDENT')) {
return $this->redirect($this->generateUrl('student_profile'));
} else {
return $this->render('ProjectEduBundle:User:selectrole.html.twig');
}
}
The first action adds role to the database and redirect is made to checkroleAction.
The if condition is not met(returns false).
I guess this happens because security context is not refreshed after the database operation of adding role is done.
How do I solve this issue?
You can set the user on the token again...
$this->get('security.context')->getToken()->setUser($userObject);
Add that at the end of your addroleAction()

Entity persists even when form has error

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.

Categories