There is a lot of questions at stack about this problem, but to be honest - no one is working for me (I am using Symfony 3.3 and FOSUserBundle 2.x).
What I need to achive:
user successfully registers at my page
his account is set automatically to default (no email is sent)
admin can activate/enable that account from his admin panel
I have created an EventListener but it won't work.. I can't even manipulate user object to change enabled status ...
#config.yml
fos_user:
db_driver: orm # other valid values are 'mongodb' and 'couchdb'
firewall_name: main
user_class: AppBundle\Entity\User
from_email:
address: "%mailer_user%"
sender_name: "%mailer_user%"
registration:
form:
type: AppBundle\Form\RegistrationType
And event listener:
class RegistrationListener implements EventSubscriberInterface
{
/**
* #var RouterInterface
*/
private $router;
private $em;
public function __construct(RouterInterface $route, UserManagerInterface $em)
{
$this->router = $route;
$this->em = $em;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
FOSUserEvents::REGISTRATION_CONFIRMED=> ['onRegistrationConfirmed', 999],
FOSUserEvents::REGISTRATION_FAILURE => 'onRegistrationFailure'
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$url = $this->router->generate('app.registration.succes');
$response = new JsonResponse(['redirectUrl' => $url]);
$event->setResponse($response);
}
public function onRegistrationFailure(FormEvent $event)
{
$array = array('success' => false, 'message' => (string)$event->getForm()->getErrors(true, true));
$response = new JsonResponse($array, Response::HTTP_BAD_REQUEST);
$event->setResponse($response);
}
public function onRegistrationConfirmed(FilterUserResponseEvent $event)
{
/**
* this one is no working.. after registration account is enabled anyway...
*/
$user = $event->getUser();
$user->setEnabled(false);
$user->setSalt('test');
$this->em->updateUser($user);
}
}
Got it!
Just call it on FOSUserEvents::REGISTRATION_SUCCESS because FOSUserEvents::REGISTRATION_CONFIRMED is triggered only when user click activation link on his email, which I'm not sending to him.
class RegistrationListener implements EventSubscriberInterface
{
/**
* #var RouterInterface
*/
private $router;
private $em;
public function __construct(RouterInterface $route, UserManagerInterface $em)
{
$this->router = $route;
$this->em = $em;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents()
{
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess',
FOSUserEvents::REGISTRATION_FAILURE => 'onRegistrationFailure'
);
}
public function onRegistrationSuccess(FormEvent $event)
{
$user = $event->getForm()->getData();
$user->setEnabled(false);
$this->em->updateUser($user);
$url = $this->router->generate('app.registration.succes');
$response = new JsonResponse(['redirectUrl' => $url]);
$event->setResponse($response);
}
public function onRegistrationFailure(FormEvent $event)
{
$array = array('success' => false, 'message' => (string)$event->getForm()->getErrors(true, true));
$response = new JsonResponse($array, Response::HTTP_BAD_REQUEST);
$event->setResponse($response);
}
}
Related
I have a problem with translation in symfony.
I have installed "symfony/translation": "4.4.*"
My translation configuration is
framework:
default_locale: "%locale%"
translator:
default_path: '%kernel.project_dir%/translations'
fallbacks:
- '%locale%'
I have an EventSubscriber called LocaleSubscriber
class LocaleSubscriber implements EventSubscriberInterface
{
private $availableLocales;
private $defaultLocale;
private $translatableListener;
protected $currentLocale;
public function __construct(
TranslatableListener $translatableListener,
LocaleRepository $localeRepository)
{
$this->translatableListener = $translatableListener;
$this->availableLocales = $localeRepository->getAvailableLocales();
$this->defaultLocale = $localeRepository->getDefaultLocale();
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::REQUEST => ['onKernelRequest', EventPriorities::PRE_WRITE],
KernelEvents::RESPONSE => ['setContentLanguage']
);
}
public function onKernelRequest(RequestEvent $event)
{
// Persist DefaultLocale in translation table
$this->translatableListener->setPersistDefaultLocaleTranslation(true);
/** #var Request $request */
$request = $event->getRequest();
if ($request->headers->has("X-LOCALE")) {
$locale = $request->headers->get('X-LOCALE');
if (in_array($locale, $this->availableLocales)) {
$request->setLocale($locale);
} else {
$request->setLocale($this->defaultLocale);
}
} else {
$request->setLocale($this->defaultLocale);
}
// Set currentLocale
$this->translatableListener->setTranslatableLocale($request->getLocale());
$this->currentLocale = $request->getLocale();
}
/**
* #param ResponseEvent $event
* #return \Symfony\Component\HttpFoundation\Response
*/
public function setContentLanguage(ResponseEvent $event)
{
$response = $event->getResponse();
$response->headers->add(array('Content-Language' => $this->currentLocale));
return $response;
}
}
Its set the language for all request with X-LOCALE header (I send it from angular app)
So I have a Controller for RecoverPassword
class RecoverPasswordAction
{
/**
* #var ValidatorInterface
*/
private $validator;
/**
* #var EntityManagerInterface
*/
private $entityManager;
/**
* #var JWTEncoderInterface
*/
private $encoder;
/**
* #var UserLoadService
*/
private $userLoadService;
/**
* #var TranslatorInterface
*/
private $translator;
public function __construct(
ValidatorInterface $validator,
EntityManagerInterface $entityManager,
JWTEncoderInterface $encoder,
UserLoadService $userLoadService,
TranslatorInterface $translator
)
{
$this->validator = $validator;
$this->entityManager = $entityManager;
$this->encoder = $encoder;
$this->userLoadService = $userLoadService;
$this->translator = $translator;
}
public function __invoke(UserRecoverPassword $data, RequestStack $requestStack)
{
**var_dump($this->translator->trans('symfony.great'));die;**
$token = $requestStack->getMasterRequest()->attributes->get('token');
$this->validator->validate($data);
try{
$vars = $this->encoder->decode($token);
$user = $this->userLoadService->getUser($vars['email']);
$this->userLoadService->recoverPasswordUser($user, $data->newPassword);
}catch (\Exception $exception){
return new JsonResponse(['status' => 'FAILED', 'message' => $this->translator->trans($exception->getMessage())],500);
}
//return new JsonResponse(['status' => 'OK', 'message' => 'The password of '.$user->getUsername().' was recover']);
return new JsonResponse([
'status' => 'OK',
'message' => $this->translator->trans(
'user.success',
[],
'messages',
$requestStack->getCurrentRequest()->getLocale()
)
]);
}
}
But when I request this endpoint, I get this response
string(13) "symfony.great"
However the validation errors runs ok, I don't know why. what am i missing?
i need to add an extra field to the json login, currently i can POST a _username and _password to my login_check endpoint but i also need to send a _school_name so the same username can be used in different schools.
I am using the json_login (https://symfony.com/doc/current/security/json_login_setup.html) with the lexik jwt bundle. Should i create a custom controller for this or a GuardAuthenticator?
I tried extending the AbstractGuardAuthenticator and i tried the AbstractFormLoginAuthenticator but they are both not working for me. By default i used this:
login:
pattern: ^/api/v1/token
stateless: true
anonymous: true
user_checker: App\Security\UserChecker
json_login:
check_path: /api/v1/token/login
success_handler: lexik_jwt_authentication.handler.authentication_success
failure_handler: lexik_jwt_authentication.handler.authentication_failure
Then i added my custom Guard:
login:
pattern: ^/api/v1/token
stateless: true
anonymous: true
user_checker: App\Security\UserChecker
guard:
authenticators:
- App\Security\BaseAuthenticator
<?php
namespace App\Security;
use App\Entity\Client;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
class BaseAuthenticator extends AbstractGuardAuthenticator
{
const LOGIN_ROUTE = 'login_check';
private EntityManagerInterface $entityManager;
private UrlGeneratorInterface $urlGenerator;
private CsrfTokenManagerInterface $csrfTokenManager;
private UserPasswordEncoderInterface $passwordEncoder;
public function __construct(EntityManagerInterface $entityManager, UrlGeneratorInterface $urlGenerator, CsrfTokenManagerInterface $csrfTokenManager, UserPasswordEncoderInterface $passwordEncoder)
{
$this->entityManager = $entityManager;
$this->urlGenerator = $urlGenerator;
$this->csrfTokenManager = $csrfTokenManager;
$this->passwordEncoder = $passwordEncoder;
}
/**
* #param Request $request
* #return bool
*/
public function supports(Request $request)
{
if ($request->attributes->get('_route') !== static::LOGIN_ROUTE) {
return false;
}
if (!$request->isMethod(Request::METHOD_POST)) {
return false;
}
return true;
}
/**
* #param Request $request
* #return mixed
*/
public function getCredentials(Request $request)
{
return [
'client_name' => $request->request->get('client_name'),
'username' => $request->request->get('username'),
'password' => $request->request->get('password')
];
}
/**
* #param mixed $credentials
* #param UserProviderInterface $userProvider
* #return UserInterface|null
*/
public function getUser($credentials, UserProviderInterface $userProvider)
{
$userRepository = $this->entityManager->getRepository(User::class);
$clientRepository = $this->entityManager->getRepository(Client::class);
$client = $clientRepository->findOneBy([
'name' => $credentials['client_name']
]);
if (!$client instanceof Client) {
throw new CustomUserMessageAuthenticationException('Client not found');
}
$user = $userRepository->findOneBy([
'client_id' => $client->getId(),
'username' => $credentials['username']
]);
if (!$user instanceof User) {
throw new CustomUserMessageAuthenticationException('User not found');
}
return $user;
}
/**
* #param mixed $credentials
* #param UserInterface $user
* #return bool
*/
public function checkCredentials($credentials, UserInterface $user)
{
return $this->passwordEncoder->isPasswordValid($user, $credentials['password']);
}
/**
* #param Request $request
* #param TokenInterface $token
* #param string $providerKey
* #return Response|null
*/
public function onAuthenticationSuccess(Request $request, TokenInterface $token, string $providerKey)
{
return null;
}
/**
* #param Request $request
* #param AuthenticationException|null $authException
* #return Response
*/
public function start(Request $request, AuthenticationException $authException = null)
{
$data = [
// you might translate this message
'message' => 'Authentication Required'
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* #param Request $request
* #param AuthenticationException $exception
* #return JsonResponse
*/
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
$data = [
// you may want to customize or obfuscate the message first
'message' => strtr($exception->getMessageKey(), $exception->getMessageData())
// or to translate this message
// $this->translator->trans($exception->getMessageKey(), $exception->getMessageData())
];
return new JsonResponse($data, Response::HTTP_UNAUTHORIZED);
}
/**
* #return bool
*/
public function supportsRememberMe()
{
return false;
}
}
Thanks!
I am using FOSUserBundle in Symfone 2.8 webapp project. Currently the user is simply redirected to the homepage when he logs out. This should be changed to a "personal" logout page that can (optionally) display personal information (e.g. reminders for upcoming tasks or simple "Goodbey USERNAME" instead of just "Goodbey")...
So I need to access/use details of the currently logged out user. But since the user has just been logged out, I cannot access the user object any more?
How to solve this?
This is the configuration I use:
// config
security:
...
providers:
fos_userbundle:
id: fos_user.user_provider.username_email
firewalls:
main:
...
logout:
path: fos_user_security_logout
target: /logoutpage
// route
<route id="user_logout" path="/logoutpage" methods="GET">
<default key="_controller">AppBundle:Default:logout</default>
</route>
// Controller action
public function logoutAction() {
$loggedOutUser = HOW_TO_GET_USER(???);
$template = 'AppBundle:Default:logout.html.twig';
return $this->render($template, array('user' => $loggedOutUser));
}
The clean way would be to save the User's name/data in the session within an EventSubscriber/Listener that listens for a security.interactive_logout event.
The 2 problems arising thereby would be:
there is no logout event dispatched by the default LogoutHandler
symfony clears the session on logout per default configuration
You can change the session-clearing behavior by setting invalidate_session to false
security:
firewalls:
main:
# [..]
logout:
path: 'fos_user_security_logout'
target: '/logoutpage'
invalidate_session: false # <- do not clear the session
handlers:
- 'Namespace\Bridge\Symfony\Security\Handler\DispatchingLogoutHandler'
For the logout event you can create a logout handler like this:
class DispatchingLogoutHandler implements LogoutHandlerInterface
{
/** #var EventDispatcherInterface */
protected $eventDispatcher;
/**
* #param EventDispatcherInterface $event_dispatcher
*/
public function __construct(EventDispatcherInterface $event_dispatcher)
{
$this->eventDispatcher = $event_dispatcher;
}
/**
* {#inheritdoc}
*/
public function logout(Request $request, Response $response, TokenInterface $token)
{
$this->eventDispatcher->dispatch(
SecurityExtraEvents::INTERACTIVE_LOGOUT,
new InteractiveLogoutEvent($request, $response, $token)
);
}
}
Add some service configuration (or use autowiring):
Namespace\Bridge\Symfony\Security\Handler\DispatchingLogoutHandler:
class: 'Namespace\Bridge\Symfony\Security\Handler\DispatchingLogoutHandler'
arguments:
- '#event_dispatcher'
Events class
namespace Namespace\Bridge\Symfony;
final class SecurityExtraEvents
{
/**
* #Event("\Namespace\Bridge\Symfony\Security\Event\Logout\InteractiveLogoutEvent")
*/
const INTERACTIVE_LOGOUT = 'security.interactive_logout';
}
Event itself:
final class InteractiveLogoutEvent extends Event
{
/**
* #var Request
*/
protected $request;
/**
* #var Response
*/
protected $response;
/**
* #var TokenInterface
*/
protected $token;
/**
* #param Request $request
* #param Response $response
* #param TokenInterface $token
*/
public function __construct(Request $request, Response $response, TokenInterface $token)
{
$this->request = $request;
$this->response = $response;
$this->token = $token;
}
/**
* #return TokenInterface
*/
public function getToken()
{
return $this->token;
}
/**
* #return TokenInterface
*/
public function getRequest()
{
return $this->token;
}
/**
* #return Response
*/
public function getResponse()
{
return $this->response;
}
/**
* #return string
*/
public function getName()
{
return SecurityExtraEvents::INTERACTIVE_LOGOUT;
}
}
And the subscriber:
class UserEventSubscriber implements EventSubscriberInterface
{
/** #var LoggerInterface */
protected $logger;
/** #param LoggerInterface $logger */
public function __construct(LoggerInterface $logger)
{
// inject the session here
$this->logger = $logger;
}
/**
* {#inheritdoc}
*/
public static function getSubscribedEvents()
{
return array(
SecurityExtraEvents::INTERACTIVE_LOGOUT => 'onInteractiveLogout',
);
}
/**
* {#inheritdoc}
*/
public function onInteractiveLogout(InteractiveLogoutEvent $event)
{
$user = $event->getToken()->getUser();
// save the username in the session here
$this->logger->info(
'A User has logged out.',
array(
'event' => SecurityExtraEvents::INTERACTIVE_LOGOUT,
'user' => array(
'id' => $user->getId(),
'email' => $user->getEmail(),
)
)
);
}
}
Enable the subscriber by tagging it with kernel.event_subscriber
Namespace\EventSubscriber\UserEventSubscriber:
class: 'Namespace\EventSubscriber\UserEventSubscriber'
arguments: ['#monolog.logger.user']
tags:
- { name: 'kernel.event_subscriber' }
Easy huh? A somewhat dirty solution would be creating a request listener that saves the username in the session-flashbag on every request so you can get it from there in the logout-page template.
I'm using symfony 2.3 and FosUserBundle ~2.0#dev. I want to set an api key for every user after a success registration. I implemented an event listner for this purpose but it doesn't work. I didn't find where is exactly the problem.
RegistrationConfirmListener
class RegistrationConfirmListener implements EventSubscriberInterface {
private $router;
private $em;
public function __construct(UrlGeneratorInterface $router, \Doctrine\ORM\EntityManager $em) {
$this->em = $em;
$this->router = $router;
}
/**
* {#inheritDoc}
*/
public static function getSubscribedEvents() {
return array(
FOSUserEvents::REGISTRATION_SUCCESS => 'onRegistrationSuccess'
);
}
public function onRegistrationSuccess(\FOS\UserBundle\Event\FormEvent $event) {
$user = $event->getForm()->getData();
// génération d'un unique key ,ici on a le id comme prefix donc garantie pas de duplication du key
$user->setApiKey(md5(uniqid($user->getId(), TRUE)));
$url = $this->router->generate('biginfo_admin_homepage');
$event->setResponse(new RedirectResponse($url));
}
}
service.yml
services:
biginfo_user.registration_complet:
class: Biginfo\UserBundle\EventListener\RegistrationConfirmListener
arguments: [#router,#doctrine.orm.entity_manager]
tags:
- { name: kernel.event_subscriber}
You should listen for FOSUserEvents::REGISTRATION_SUCCESS event. Because REGISTRATION_COMPLETED event listener method receives a FOS\UserBundle\Event\FilterUserResponseEvent instance, which is not fit for your situation.
I have a method in 'DynamicList' service that should return a select filled with dynamic data, but i'm getting "circular reference":
YML:
parameters:
my.dynamic_list.class: My\DynamicListBundle\Service\DynamicList
services:
my.dynamic_list:
class: %my.dynamic_list.class%
arguments: ['#doctrine.orm.default_entity_manager','#templating']
Class:
<?php
namespace My\DynamicListBundle\Service;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
class DynamicList
{
private $em;
private $templating;
public function __construct(
EntityManager $em,
EngineInterface $templating
) {
$this->em = $em;
$this->templating = $templating;
}
public function getSelect($slug)
{
$dynamic_list = $this->em
->getRepository('MyDynamicListBundle:DynamicList')
->findOneBy(array(
"slug" => $slug
));
return $this->templating->render('MyComponentsCoreBundle::Templates/DynamicList/combo.html.twig', array(
'dl' => $dynamic_list
));
}
}
I guess i don't need to put here the twig content: the problema occurs before.
Last, the error i'm getting:
Circular reference detected for service "my.dynamic_list", path: "my.dynamic_list -> templating -> twig". (500 Internal Server Error - ServiceCircularReferenceException)
What's the proper way to get templating component working in my service?
Well, I found a workaround but I don't if is the best way:
<?php
namespace My\DynamicListBundle\Service;
use Doctrine\ORM\EntityManager;
use Symfony\Bundle\FrameworkBundle\Templating\EngineInterface;
class DynamicList
{
private $em;
private $templating;
public function __construct(
EntityManager $em
) {
$this->em = $em;
}
public function init(
EngineInterface $templating
) {
$this->templating = $templating;
}
public function getSelect($slug)
{
$dynamic_list = $this->em
->getRepository('MyDynamicListBundle:DynamicList')
->findOneBy(array(
"slug" => $slug
));
return $this->templating->render('MyComponentsCoreBundle::Templates/DynamicList/combo.html.twig', array(
'dl' => $dynamic_list
));
}
}
So, in controller, I call 'init()' to pass 'templating':
$dl_service = $this->get('my.dynamic_list');
$dl_service->init($this->container->get('templating'));