i have problem with HWIOAuthBundle and google authentication, i can't complete work on OAuthProvider. After flush data, i want return entity object, that i saw example somewhere in stackoverflow.
But when i return $obj;
I catch error :
Catchable Fatal Error: Argument 2 passed to HWI\Bundle\OAuthBundle\Security\Core\Authentication\Token\OAuthToken::__construct() must be of the type array, integer given, called in /var/www/integra/vendor/hwi/oauth-bundle/Security/Core/Authentication/Provider/OAuthProvider.php on line 109 and defined
Construct this class :
public function __construct($accessToken, array $roles = array())
{
parent::__construct($roles);
$this->setRawToken($accessToken);
parent::setAuthenticated(count($roles) > 0);
}
Then i return:
return new JsonResponse(['accessToken' => $user->getToken(), 'Roles' => $user->getRoles()]); // I catch error what it loadUserByOAuthUserResponse() must return a UserInterface
class OAuthProvider extends OAuthUserProvider
{
protected $container, $em;
public function __construct(\Doctrine\ORM\EntityManager $em, $container)
{
$this->container = $container;
$this->em = $em;
}
public function loadUserByOAuthUserResponse(UserResponseInterface $response)
{
$email = $response->getEmail();
if ($email === null) {
throw new NotFoundHttpException("User email adress not found", 404);
}
$name = $response->getFirstName();
$surname = $response->getLastName();
$photo = $response->getProfilePicture();
$repository = $this->em->getRepository('IdamasBillingBundle:User');
$user = $repository->searchByNameSurnameEmail($email);
if($user){
$login = new User();
$login->setEmail($email);
$session = $this->container->get('session');
$session->set('login', $login);
return $user;
} else {
$user = new User();
$user->setEmail($email);
$user->setName($name);
$user->setSurname($surname);
$user->setPosition('Just user');
$user->setRoles(1);
$user->setPhoto($photo);
$this->em->persist($user);
$this->em->flush();
$session = $this->container->get('session');
$session->set('login', $user);
// return $user;f
return new JsonResponse(['accessToken' => $user->getToken(), 'Roles' => $user->getRoles()]);
}
//return new RedirectResponse("/billing");
}
}
How i can to do it, that redirect to complete login page?
User object should have roles property, and it must be an array:
class User {
protected $roles = [];
public function getRoles() {
return $this->roles;
}
public function addRole($role) {
$this->roles[] = $role;
}
}
Related
im trying to make a login method with symfony 4 , i have create this method public function
getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
return new RedirectResponse($this->urlGenerator->generate('app_login'));
}
return $user;
}
when i put a false user it display to me this error:
The "App\Security\UserAuthenticator::getUser()" method must return a UserInterface. You returned "Symfony\Component\HttpFoundation\RedirectResponse".
I'm guessing you are using a simple FormAuthenticator extending AbstractFormLoginAuthenticator (which is deprecated for symfony 5.3+ with the new authenticator system).
getUser() should only return an user or throw an exception so you should remove your redirect with:
public function getUser($credentials, UserProviderInterface $userProvider)
{
$token = new CsrfToken('authenticate', $credentials['csrf_token']);
if (!$this->csrfTokenManager->isTokenValid($token)) {
throw new InvalidCsrfTokenException();
}
$user = $this->entityManager->getRepository(User::class)->findOneBy(['username' => $credentials['username']]);
if (!$user) {
throw new UserNotFoundException('User not found.');
}
return $user;
}
(You may want to use a less descriptive exception for security reason).
If I remember how it looked like in 4.4, your login method in your controller should look like:
use Symfony\Component\HttpFoundation\Request\AuthenticationUtils;
/**
* #Route("/login", name="app_login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('security/login.html.twig',
[
'last_username' => $lastUsername,
'error' => $error,
]
);
}
I'm writing a method in my Symfony 3 application for bulk user creation. The flux is uploading a csv file with all the necessary data.
I created a Service, into I write all the logic of this operation. This is my Service:
class BulkRegistration
{
private $em;
private $validator;
private $session;
public function __construct(EntityManagerInterface $em, ValidatorInterface $validator, SessionInterface $session)
{
$this->em = $em;
$this->validator = $validator;
$this->session = $session;
}
public function run(BulkRegistrationData $bulkRegistrationData){
//todo rimuovere dipendenza nascosta
$serializer = new Serializer([new ObjectNormalizer()], [new CsvEncoder()]);
$datas = $serializer->decode(file_get_contents($bulkRegistrationData->csv), 'csv');
$this->em->getConnection()->beginTransaction();
try{
foreach($datas as $data)
{
$userData = UserData::create($data);
$this->validate($userData, 'newUser');
$userCreate = User::create($userData->user);
$this->em->persist($userCreate);
$this->em->flush();
}
$this->em->getConnection()->commit();
} catch (\Exception $e) {
$this->em->getConnection()->rollback();
$this->em->close();
$this->session->getFlashBag()->add('error', $e->getMessage());
return false;
}
return true;
}
private function validate ($entity, $validationGroup = null){
if($validationGroup){
$errors = $this->validator->validate($entity, null, [$validationGroup]);
}else{
$errors = $this->validator->validate($entity);
}
if (count($errors) > 0) {
$errorMessage = '';
foreach($errors as $error)
{
$errorMessage .= $error->getMessage();
}
throw new \Exception($errorMessage);
}
return;
}
}
Also I wrote this EmailSubscriber, for sending an activation email each time the entity User is persisted:
class EmailSubscriber implements EventSubscriber
{
private $activationEmail;
public function __construct(SendActivationEmail $activationEmail)
{
$this->activationEmail = $activationEmail;
}
public function getSubscribedEvents()
{
return array(
Events::postPersist,
);
}
public function postPersist(LifecycleEventArgs $args)
{
$entity = $args->getObject();
$entityManager = $args->getObjectManager();
if ($entity instanceof User)
{
$this->activationEmail->send($entity);
}
}
}
And this is question:
The EventSubscriber catch the persisted event before the transaction commit.
I want or persist all the row in my db, or response with violation and ask to User to modify his csv file.
Because this, one of the useCase can be some activation email sended but no persisting the User in DB, for example for some validate violation of one of the csv row.
I hope I was crearl, the case is a bit intricate.
I think you need to adjust the foreach to only flush if there were no errors:
foreach($datas as $data) {
$userData = UserData::create($data);
try {
$this->validate($userData, 'newUser');
} catch (\Exception $e) {
$this->em->getConnection()->rollback();
$this->em->close();
$this->session->getFlashBag()->add('error', $validation);
return false;
}
$userCreate = User::create($userData->user);
$this->em->persist($userCreate);
}
$this->em->flush();
$this->em->getConnection()->commit();
return true;
I am using JWT Token Bundle for user authentication. When the token is expired I get 500 server error. Instead of this how can I return JsonResponse with error code and message?
Here is my authenticator class:
class JwtTokenAuthentication extends AbstractGuardAuthenticator
{
/**
* #var JWTEncoderInterface
*/
private $jwtEncoder;
/**
* #var EntityManager
*/
private $em;
public function __construct(JWTEncoderInterface $jwtEncoder, EntityManager $em)
{
$this->jwtEncoder = $jwtEncoder;
$this->em = $em;
}
public function getCredentials(Request $request)
{
$extractor = new AuthorizationHeaderTokenExtractor(
'Bearer',
'Authorization'
);
$token = $extractor->extract($request);
if (!$token) {
return null;
}
return $token;
}
public function getUser($credentials, UserProviderInterface $userProvider)
{
$data = $this->jwtEncoder->decode($credentials);
if(!$data){
return null;
}
$user = $this->em->getRepository("AlumnetCoreBundle:User")->find($data["email"]);
return $user;
}
public function checkCredentials($credentials, UserInterface $user)
{
return true;
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
//todo
}
public function start(Request $request, AuthenticationException $authException = null)
{
return new JsonResponse([
'errorMessage' => 'auth required'
], Response::HTTP_UNAUTHORIZED);
}
}
You can decode the token in a try-catch:
try {
$data = $this->jwtEncoder->decode($credentials);
} catch (\Exception $e) {
throw new \Symfony\Component\Security\Core\Exception\BadCredentialsException($e->getMessage(), 0, $e);
}
But you might have to implement the missing onAuthenticationFailure since throwing this exception will make it called. Something like:
public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
{
return new JsonResponse([
'errorMessage' => $exception->getMessage(),
], Response::HTTP_UNAUTHORIZED);
}
Btw, LexikJWTAuthenticationBundle comes with a built-in JWTTokenAuthenticator since its 2.0 version. I suggest you to try using it before implementing your own authenticator, or at least extend it.
I surround my entire code in a try catch block, when the JWT Token Expired error message is generated, it is caught in the catch block.
{
"error": 1,
"status": 400,
"msg": "Expired JWT Token",
"data": []
}
/**
* #Route("/api/tokens")
* #Method("POST")
*/
public function newTokenAction(Request $request)
{
try {
$data['_username'] = $request->get('_username');
$data['_password'] = $request->get('_password');
if (empty($data['_username']) || empty($data['_password'])) {
throw new \Exception('Username or password fields empty');
}
$user = $this->getDoctrine()->getRepository('AppBundle:User')->findOneBy(array('username' => $data['_username']));
if (!$user) {
throw new \Exception('Username or password does not exist');
} else if ($user->hasRole('ROLE_SUPER_ADMIN')) {
throw new \Exception('Admin is not allowed to login through app');
} else if (!$user->getEnabled()) {
throw new \Exception('User is not enabled');
} else if ($user->getIsDeleted()) {
throw new \Exception('User does not exist any more');
}
$isValid = $this->get('security.password_encoder')->isPasswordValid($user, $data['_password']);
if (!$isValid) {
throw new \Exception('Bad Credentials');
}
$token = $this->get('lexik_jwt_authentication.encoder')->encode(array(
'username' => $data['_username'],
'exp' => time() + 3600,
'secret_key' => ____________,
));
$user->setAuthToken($token);
$em = $this->getEntityManager();
$em->persist($user);
$em->flush();
$json = $this->getJsonResponse(0, 200, 'User Logged In');
$response = new Response($json);
$response->headers->set('Content-Type', 'application/json');
return $response;
} catch (\Exception $e) {
// Using custom Execption class
$customApiProblem = new CustomApiProblem(self::API_ERROR_TRUE, $httpStatusCode, $e->getMessage());
$customApiProblem->set('data', $data);
$serializer = $this->container->get('jms_serializer');
$response_json = $serializer->serialize($customApiProblem->toArray(), 'json');
return new Response($response_json, $statusCode);
}
}
I'm working on a creating a custom authentication provider. I've written my own Authentication Provider, Listener, Token and everything. It's based off a form login, and I've stepped through the code and everything seems to be configured properly. Everything is called in the right order, and my authentication provider is invoked perfectly. The authentication provider successfully authenticates the user, and returns the authenticated token. I extend AbstractAuthenticationListener which, in the handle method, will set the security context.
The user seems to be logged in, but in the debug toolbar, the token is not set and I see "You are not authenticated" and "No token".
Is there any configuration settings that I'm missing? Why would the user would be logging in, authentication provider returning successfully, with an authenticated token, being set in the security context but still be not authenticated? Any tips on how to debug this?
(I will post code as needed.)
EDIT: Token Definition:
This is very simple, just extending from AbstractToken:
class UserToken extends AbstractToken
{
private $username;
private $password;
private $domain;
private $providerKey;
public function __construct($username, $password, $domain, $provider_key, array $roles = array('ROLE_USER'))
{
parent::__construct($roles);
$this->username = $username;
$this->password = $password;
$this->domain = $domain;
$this->providerKey = $provider_key;
}
public function getCredentials()
{
return '';
}
function getUsername() {
return $this->username;
}
function getDomain() {
return $this->domain;
}
function getPassword() {
return $this->password;
}
function getProviderKey(){
return $this->providerKey;
}
}
Authentication Listener:
class Listener extends AbstractAuthenticationListener
{
protected $authenticationManager;
public function __construct(
SecurityContextInterface $securityContext,
AuthenticationManagerInterface $authenticationManager,
SessionAuthenticationStrategyInterface $sessionStrategy,
HttpUtils $httpUtils,
$providerKey,
AuthenticationSuccessHandlerInterface $successHandler,
AuthenticationFailureHandlerInterface $failureHandler,
array $options = array(),
LoggerInterface $logger = null,
EventDispatcherInterface $dispatcher = null
//CsrfProviderInterface $csrfProvider = null
) {
parent::__construct(
$securityContext,
$authenticationManager,
$sessionStrategy,
$httpUtils,
$providerKey,
$successHandler,
$failureHandler,
array_merge(
array(
'username_parameter' => '_username',
'password_parameter' => '_password',
'domain_parameter' => '_domain',
'csrf_parameter' => '_csrf_token',
'intention' => 'authenticate',
'post_only' => true,
),
$options
),
$logger,
$dispatcher
);
}
/**
* Performs authentication.
*
* #param Request $request A Request instance
*
* #return TokenInterface|Response|null The authenticated token, null if full authentication is not possible, or a Response
*
* #throws AuthenticationException if the authentication fails
*/
protected function attemptAuthentication(Request $request)
{
// Create initial unauthenticated token and pass data to the authentication manager.
// TODO validate request data.
$username = trim($request->request->get($this->options['username_parameter'], null, true));
$password = $request->request->get($this->options['password_parameter'], null, true);
$domain = $request->request->get($this->options['domain_parameter'], null, true);
$token = $this->authenticationManager->authenticate(new UserToken($username, $password, $domain, $this->providerKey));
return $token;
}
}
The above code will invoke the the auth function on the provider via the AuthenticationManager:
//This is from the AuthenticationProvider
public function authenticate(TokenInterface $token) {
$loginHandler = new LoginAuthenticationHandler($token->getUsername(), $token->getPassword(), $token->getDomain());
//This code just calls our web service and authenticates. I removed some business logic here, but this shows the gist of it.
if(!$boAuthenticationToken = $loginHandler->authenticate())
{
throw new AuthenticationException('Bad credentials');
}
else{
$user = $this->userProvider->loadUserByUsername($token->getUsername());
//$user = $this->userProvider->getUser($token, $boAuthenticationToken);
// Set the user which will be invoked in the controllers.
$token->setUser($user);
$token->setAuthenticated(true);
return $token;
}
}
Bundle Services.yml
parameters:
services:
ws.security.authentication.provider:
#http://blog.vandenbrand.org/2012/06/19/symfony2-authentication-provider-authenticate-against-webservice/
class: Aurora\OurCustomBundle\Security\Authentication\Provider\Provider
arguments: ["bo_remove_this_with_bo_auth_service", "", "#security.user_checker", "", "#security.encoder_factory"]
ws.security.authentication.listener:
class: Aurora\OurCustomBundle\Security\Firewall\Listener
parent: security.authentication.listener.abstract
abstract: true
#arguments: []
arguments: ["#security.context", "#security.authentication.manager", "#security.authentication.session_strategy", "#security.http_utils", "ws.user_provider", "#security.authentication.customized_success_handler", "#main.cas.rest.user.authentication.failure.service"]
ws.user_provider:
class: Aurora\OurCustomBundle\Security\User\UserProvider
And lastly, the UserProvider
class UserProvider implements UserProviderInterface
{
public function loadUserByUsername($username)
{
//Just return a simple user for now.
return new User($username, array('ROLE_USER'));
}
public function refreshUser(UserInterface $user)
{
if (!$user instanceof User) {
throw new UnsupportedUserException(
sprintf('Instances of "%s" are not supported.', get_class($user))
);
}
return $this->loadUserByUsername($user->getUsername());
}
public function supportsClass($class)
{
return $class === 'Aurora\OurCustomBundle\Security\User\User';
}
}
After many hours of hair pulling, I figured out the problem!
The token implementation was incorrect. Since I was implementing my own Token, which extends from AbstractToken, I needed to also implement the serialize() and unserialize() functions.
Once I did that, the code worked. The updated Token class is below for future reference:
class UserToken extends AbstractToken
{
private $username;
private $password;
private $domain;
private $providerKey;
public function __construct($username, $password, $domain, $provider_key, array $roles = array('ROLE_USER'))
{
parent::__construct($roles);
$this->username = $username;
$this->password = $password;
$this->domain = $domain;
$this->providerKey = $provider_key;
}
public function getCredentials()
{
return '';
}
function getUsername() {
return $this->username;
}
function getDomain() {
return $this->domain;
}
function getPassword() {
return $this->password;
}
function getProviderKey(){
return $this->providerKey;
}
function serialize(){
return serialize(array($this->username, $this->password, $this->domain, parent::serialize()));
}
function unserialize($serialized){
list($this->username, $this->password, $this->domain, $parentSerialization) = unserialize($serialized);
parent::unserialize($parentSerialization);
}
}
I would like to translate in my ajax login error messages.
I am using FOSUserBundle, and I would take advantage of the translation files I properly overwritten folder Resources/translations.
My AthenticationHandler.php:
class AuthenticationHandler
implements AuthenticationSuccessHandlerInterface,
AuthenticationFailureHandlerInterface
{
protected $router;
protected $security;
protected $userManager;
protected $service_container;
public function __construct(RouterInterface $router,SecurityContext $security, $userManager, $service_container)
{
$this->router = $router;
$this->security = $security;
$this->userManager = $userManager;
$this->service_container = $service_container;
}
public function onAuthenticationSuccess(Request $request, TokenInterface $token) {
if ($request->isXmlHttpRequest()) {
//...
}
return new RedirectResponse($this->router->generate('anag_new'));
}
public function onAuthenticationFailure(Request $request, AuthenticationException $exception) {
if ($request->isXmlHttpRequest()) {
$error = $exception->getMessage();
$result = array('success' => false, 'message' => $request->get('translator')->trans($error, array(), 'FOSUserBundle'));
$response = new Response(json_encode($result));
$response->headers->set('Content-Type', 'application/json');
return $response;
} else {
//...
}
}
}
But return this error:
Fatal error: Call to a member function trans() on a non-object in /var/www/MyBusiness/src/My/UserBundle/Handler/AuthenticationHandler.php
How do I translate the error messages?
$request->get(string $key, mixed $default = null, type $deep = false) is for getting parameters from GET, PATH, POST, COOKIE. You are trying to get a "translator" parameter, which probably doesn't exist. So null is returned and you call trans() on a non-object.
Simply inject the "translator" service in your AuthenticationHandler (in the constructor, probably). Or get it from the service container, as it's available in your class:
$translator = $this->service_container->get('translator');
// ...
$result = array(
'success' => false,
'message' => $translator->trans($error, array(), 'FOSUserBundle')
);