src/WebBundle/Controller/UserController.php line 229

Open in your IDE?
  1. <?php
  2. namespace WebBundle\Controller;
  3. use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use Exception;
  6. use FlexApp\Constant\TimeConstant;
  7. use FlexApp\Exceptions\PortalApiException;
  8. use FlexApp\Helper\SecuriHelper;
  9. use FlexApp\Security\LoginFormAuthenticator;
  10. use FlexApp\Service\Canonicalizer;
  11. use FlexApp\Service\ManualReg\UserLogger;
  12. use InvalidArgumentException;
  13. use Symfony\Component\HttpFoundation\Cookie;
  14. use Symfony\Component\HttpFoundation\JsonResponse;
  15. use Symfony\Component\HttpFoundation\RedirectResponse;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\HttpFoundation\RequestStack;
  18. use Symfony\Component\HttpFoundation\Response;
  19. use Symfony\Component\HttpFoundation\Session\Session;
  20. use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
  21. use Symfony\Component\Security\Core\Security;
  22. use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
  23. use WebBundle\Entity\OrderAddress;
  24. use WebBundle\Entity\User;
  25. use WebBundle\Helper\App;
  26. use WebBundle\Helper\CookieHelper;
  27. use WebBundle\Helper\SocialLoginHelper;
  28. use WebBundle\Helper\UserHelper;
  29. use WebBundle\Repository\UserRepository;
  30. use WebBundle\Service\OrderService;
  31. use WebBundle\Service\RegistrationService;
  32. use WebBundle\Service\SocialLoginService;
  33. use function Symfony\Component\String\u;
  34. /**
  35.  * User controller.
  36.  */
  37. class UserController extends ExtendedController
  38. {
  39.     const URL_TILE_TEST_AUTH 'https://tile.expert/json/set_cookies_test/process';
  40.     const COOKIE_DELIMITER ':';
  41.     /** @required */
  42.     public UserAuthenticatorInterface $userAuthenticator;
  43.     /** @required */
  44.     public LoginFormAuthenticator $loginFormAuthenticator;
  45.     /** @required */
  46.     public RequestStack $requestStack;
  47.     /** @required */
  48.     public EntityManagerInterface $entityManager;
  49.     /** @required */
  50.     public UserPasswordHasherInterface $userPasswordHasher;
  51.     /** @required */
  52.     public UserLogger $userLogger;
  53.     /** @required */
  54.     public UserRepository $userRepository;
  55.     /** @required */
  56.     public Canonicalizer $canonicalizer;
  57.     protected OrderService $orderService;
  58.     private array $messages = [
  59.         'Bad credentials.' => 'user_login_bad'
  60.         // 'Invalid CSRF token.' => 'user_login_bad'
  61.     ];
  62.     private array $patterns = [
  63.         'login' => UserHelper::VALID_SYMBOL_LOGIN// "/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[_!@#$%&])(?=.{4,})/i",
  64.         'password' => UserHelper::VALID_SYMBOL_PASSWORD//  "/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[_!@#$%&])(?=.{4,})/i",
  65.         'email' => UserHelper::VALID_SYMBOL_EMAIL// "/^([a-zA-Z0-9_\.-]+\@[\da-zA-Z\.-]+\.[a-zA-Z\.]{2,6})$/i",
  66.     ];
  67.     /**
  68.      * @throws Exception
  69.      */
  70.     public function loginWithoutLocaleAction(): RedirectResponse
  71.     {
  72.         return new RedirectResponse(App::generateUrl('app_login'));
  73.     }
  74.     /**
  75.      * @throws Exception
  76.      */
  77.     public function logoutWithoutLocaleAction(): RedirectResponse
  78.     {
  79.         return new RedirectResponse(App::generateUrl('app_logout'));
  80.     }
  81.     /**
  82.      * @param Request $request
  83.      * @return Response
  84.      * @throws Exception
  85.      */
  86.     public function loginAction(Request $request)
  87.     {
  88.         /** @var Session $session */
  89.         $session $request->getSession();
  90.         $user App::getCurUser();
  91.         if ($user) {
  92.             if (!empty($_SERVER['HTTP_REFERER'])) {
  93.                 $referer $_SERVER['HTTP_REFERER'];
  94.                 return new RedirectResponse($referer);
  95.             }
  96.             return $this->redirectToRoute('app_home', ['_locale' => App::getCurLocale()]);
  97.         }
  98.         $redirectUrl $request->query->get('redirect_url');
  99.         if ($redirectUrl) {
  100.             $request->getSession()->set('login_referer'$redirectUrl);
  101.         }
  102.         $authenticationUtils $this->get('security.authentication_utils');
  103.         if (!$request->getSession()->has('_authenticationError')) {
  104.             $request->getSession()->set('_authenticationError'0);
  105.         }
  106.         // получить ошибки логина, если таковые имеются
  107.         if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
  108.             $error $request->attributes->get(Security::AUTHENTICATION_ERROR);
  109.         } else {
  110.             $error $session->get(Security::AUTHENTICATION_ERROR);
  111.             //Bad credentials.
  112.         }
  113.         $errorMessageBad $error && isset($this->messages[$error->getMessage()])
  114.             ? $this->get('translator')->trans($this->messages[$error->getMessage()])
  115.             : null;
  116.         if ($error && !$errorMessageBad) {
  117.             $errorMessageBad $error->getMessage();
  118.         }
  119.         // Считаем кол-во попыток
  120.         $authError $authenticationUtils->getLastAuthenticationError();
  121.         if ($authError !== null) {
  122.             if ($request->getSession()->has('_authenticationError')) {
  123.                 // Сохраняем предыдущий результат
  124.                 $prevError $request->getSession()->get('_authenticationError');
  125.                 // Удаляем предыдущий результат
  126.                 $request->getSession()->remove('_authenticationError');
  127.                 // Сохраняем новый
  128.                 $request->getSession()->set('_authenticationError'$prevError 1);
  129.             }
  130.         }
  131.         // Проверка на кол-во ошибок ввода
  132.         $authenticationError $request->getSession()->has('_authenticationError') &&
  133.             $request->getSession()->get('_authenticationError') >= 5;
  134.         if ($authenticationError) {
  135.             if (!$request->getSession()->has('_authenticationWait')) {
  136.                 $request->getSession()->set('_authenticationWait'time() + 10 60);
  137.             }
  138.         }
  139.         if ($request->getSession()->has('_authenticationWait')) {
  140.             if ($request->getSession()->get('_authenticationWait') <= time()) {
  141.                 $request->getSession()->remove('_authenticationWait');
  142.                 $request->getSession()->remove('_authenticationTimeWait');
  143.                 $request->getSession()->set('_authenticationError'0);
  144.                 $authenticationError false;
  145.             } else {
  146.                 $request->getSession()->set('_authenticationTimeWait'$request->getSession()->get('_authenticationWait') - time());
  147.             }
  148.         }
  149.         $csrfToken $this->get('security.csrf.token_manager')->getToken('authenticate')->getValue();
  150.         /**
  151.          * @var $referer
  152.          * Выбирает реферер ссылку из заголовка и помещает в сессионную переменную, для использования в логине,
  153.          * если мы ранее не находиль на странице авторизации /{_locale}/login
  154.          */
  155.         $logger App::getContainer()->get('logger_public');
  156.         $referer $request->headers->get('referer');
  157.         $logger->info('получаем referer ' $referer);
  158.         $refererSession $request->getSession()->get('login_referer');
  159.         $logger->info('получаем refererSession ' $refererSession);
  160.         if (strpos($refererSession'css') !== false) {
  161.             $request->getSession()->remove('login_referer');
  162.             $logger->error('remove login_referer ' $refererSession);
  163.         }
  164.         if ($referer) {
  165.             if (strpos($referer'login') === false && strpos($referer'css') === false) {
  166.                 $request->getSession()->set('login_referer'$referer);
  167.                 $logger->info('set login_referer login ' $referer);
  168.             }
  169.         } else {
  170.             $logger->error('set login_referer no ' $referer);
  171.         }
  172.         $logger->info('check login ' strpos($referer'login') . ' ' $referer);
  173.         $logger->info('check css ' strpos($referer'css') . ' ' $referer);
  174.         $socialLogClent = new SocialLoginHelper($this->container);
  175.         return $this->render('@Web/User/login.html.twig', [
  176.             // имя, введённое пользователем в последний раз
  177.             'last_username' => $session->get(Security::LAST_USERNAME),
  178.             'csrf_token' => $csrfToken,
  179.             'token' => UserHelper::getInstance()->getToken(),
  180.             'authenticationError' => $authenticationError,
  181.             'authenticationTimeWait' => $request->getSession()->get('_authenticationTimeWait'null),
  182.             'error' => $error,
  183.             'errorMessageBad' => $errorMessageBad,
  184.             'vkUrl' => $socialLogClent->getVkUrl(),
  185.             'instagramUrl' => $socialLogClent->getInstagramUrl(),
  186.             'googleUrl' => $socialLogClent->googleClient()->createAuthUrl(),
  187.             'facebookUrl' => $socialLogClent->getFacebookUrl(),
  188.             'locale' => App::getCurLocale()
  189.         ]);
  190.     }
  191.     /**
  192.      * @param Request $request
  193.      * @return RedirectResponse|Response
  194.      * @throws Exception
  195.      */
  196.     function registrationAction(Request $request)
  197.     {
  198.         $referer $this->get('request_stack')->getCurrentRequest()->headers->get('referer');
  199.         $session $this->get('session');
  200.         if (strpos($referer'chat') !== false) {
  201.             $session->set('target_url_for_chat'$this->generateUrl('app_chatb_chatbpage'));
  202.         }
  203.         /** @var RegistrationService $registrationService */
  204.         $registrationService $this->get('app.registration');
  205.         /** @var User $user */
  206.         try {
  207.             $user $registrationService->register($request);
  208.         } catch (UniqueConstraintViolationException $e) {
  209.             return $this->redirectToRoute('app_logout');
  210.         }
  211.         $error_message null;
  212.         // Если получилось зарегистрировать пользователя
  213.         if ($user) {
  214.             // То авторизуем его
  215.             $this->userAuthenticator->authenticateUser($user$this->loginFormAuthenticator$this->requestStack->getCurrentRequest());
  216. //            $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
  217. //            $this->get('security.token_storage')->setToken($token);
  218. //            $session->set('_security_main', serialize($token));
  219.             // И редиректим на домашнюю страницу
  220.             $url $this->generateUrl('app_home');
  221.             // Если перешел по ссылке из чата, то редиректим обратно в чат, чтобы не потерять клиента,
  222.             // и чтобы не заставлять его переходить с главной страницы сначала в контакты, а оттуда в чат.
  223.             if ($session->has('target_url_for_chat')) {
  224.                 $url $session->get('target_url_for_chat');
  225.                 $session->remove('target_url_for_chat');
  226.             }
  227.             if ($session->has('login_referer')) {
  228.                 $url $session->get('login_referer');
  229.                 $this->orderService $this->get('app.service.order');
  230.                 $this->orderService->checkOrderByLink($url$user);
  231.             }
  232.             return new RedirectResponse($url);
  233.         }
  234.         $socialLogClent = new SocialLoginHelper($this->container);
  235.         $response $this->render(
  236.             '@Web/User/registration.html.twig',
  237.             [
  238.                 'ip' => $request->getClientIp(),
  239.                 'token' => UserHelper::getInstance()->getToken(),
  240.                 'form' => $registrationService->getRegistrationForm()->createView(),
  241.                 'vkUrl' => $socialLogClent->getVkUrl(),
  242.                 'instagramUrl' => $socialLogClent->getInstagramUrl(),
  243.                 'googleUrl' => $socialLogClent->googleClient()->createAuthUrl(),
  244.                 'facebookUrl' => $socialLogClent->getFacebookUrl(),
  245.                 'locale' => App::getCurLocale()
  246.             ]
  247.         );
  248.         return $response;
  249.     }
  250.     /**
  251.      * Авторизация через соц.сети
  252.      * @param Request $request
  253.      * @return Response
  254.      * @throws Exception
  255.      */
  256.     public function socialLoginAction(Request $request): Response
  257.     {
  258.         $successUrl $this->generateUrl('app_home');
  259.         /** @var SocialLoginService $socialLoginService */
  260.         $socialLoginService $this->get("app.social_login");
  261.         if ($request->get('formEmail')) {
  262.             $result $socialLoginService->socialLogin(00$request->request->all());
  263.             if (gettype($result) == 'array' && isset($result['confirmEmail'])) {
  264.                 return $this->render('@Web/User/slogin-email.html.twig'array_merge($result['confirmEmail'], ['locale' => App::getCurLocale(), 'confirmEmail' => true]));
  265.             }
  266.         } elseif ($request->get('confirmEmail')) {
  267.             $data $request->request->all();
  268.             $result $socialLoginService->confirmEmailByPassword($data['email'], $data['password'], $data);
  269.             if (!$result) {
  270.                 return $this->render('@Web/User/slogin-email.html.twig'array_merge($data, ['locale' => App::getCurLocale(), 'confirmEmail' => true'error' => true]));
  271.             }
  272.         } else {
  273.             $network $request->get("network");
  274.             $networkCode $request->get("code");
  275.             $result $socialLoginService->socialLogin($networkCode$network);
  276.             if (isset($result['formEmail'])) {
  277.                 return $this->render('@Web/User/slogin-email.html.twig'array_merge($result['formEmail'], ['locale' => App::getCurLocale()]));
  278.             } elseif (isset($result['confirmEmail'])) {
  279.                 return $this->render('@Web/User/slogin-email.html.twig'array_merge($result['confirmEmail'], ['locale' => App::getCurLocale(), 'confirmEmail' => true]));
  280.             }
  281.         }
  282.         return $this->redirect(
  283.             $result
  284.                 $successUrl
  285.                 App::generateUrl('app_login', ['_locale' => App::getCurLocale()])
  286.         );
  287.     }
  288.     /**
  289.      * Добавление - изменение пользователя с портала
  290.      * согласовано поле идентификатор - username
  291.      *
  292.      * @param Request $request
  293.      * @return JsonResponse
  294.      * @throws PortalApiException|Exception
  295.      */
  296.     public function addAction(Request $request): JsonResponse
  297.     {
  298.         $this->userLogger->log('Запрос с портала: ' $request->getContent());
  299.         $data json_decode($request->getContent(), true);
  300.         $docum $data['document'];
  301.         if (!u($docum['emailCanonical'])->containsAny(['@tile.expert''@treto.ru'])) {
  302.             return new JsonResponse(['success' => false'message' => "Ошибка валидации: emailCanonical '{$docum['emailCanonical']}' не содержит @tile.expert или @treto.ru"], Response::HTTP_BAD_REQUEST);
  303.         }
  304.         // Договорились идентифицировать пользователя по единственному полю - "username": https://te2.remote.team/discus/13D76A4A-5404-BE01-BFF1-DC000ED813BE
  305.         $user $this->userRepository->findOneBy(['username' => $this->canonicalizer->canonicalize($docum['username'])]);
  306.         $isNewUser false;
  307.         if (!$user) {
  308.             $isNewUser true;
  309.             $user = new User();
  310.             // генерируем только при создании пользователя
  311.             $user->setToken(md5(date('Y-m-dH:i:s')));
  312.             $orderAddress = new OrderAddress();
  313.             $orderAddress->setIsMainRecipient(true);
  314.             $orderAddress->setToken($user->getToken());
  315.             $user->addOrderAddress($orderAddress);
  316.             $this->entityManager->persist($user);
  317.         }
  318.         if ($docum['unid'] ?? '') {
  319.             $anotherUserWithTheSameUnid $this->userRepository->findOneByUnid($docum['unid'] ?? '');
  320.             if ($anotherUserWithTheSameUnid && $anotherUserWithTheSameUnid->getId() !== $user->getId()) {
  321.                 return new JsonResponse(['success' => false'message' => "User with such unid {$docum['unid']} already exists: {user id: $anotherUserWithTheSameUnid->getId()} email: {$anotherUserWithTheSameUnid->getEmail()} username: '{$anotherUserWithTheSameUnid->getUsername()}'"], Response::HTTP_BAD_REQUEST);
  322.             }
  323.             $user->setUnid($docum['unid']);
  324.         } else {
  325.             if ($isNewUser) {
  326.                 return new JsonResponse(['success' => false'message' => "Поле unid является обязательным для новых пользователей."]);
  327.             }
  328.         }
  329.         $user->setEmail($docum['emailCanonical']);
  330.         $user->setUsername($this->canonicalizer->canonicalize($docum['username']));
  331.         $user->setAlias($docum['portalData']['alias']);
  332.         if ($docum['portalData']['plainPassword'] ?? null) {
  333.             $user->setPlainPassword($docum['portalData']['plainPassword']);
  334.         } else {
  335.             if ($isNewUser) {
  336.                 return new JsonResponse(['success' => false'message' => "Пользователь username {$user->getUsername()} не найден в базе данных. Для новых пользователей поле plainPassword является обязательным."]);
  337.             } else {
  338.                 $user->setPlainPassword(null);
  339.             }
  340.         }
  341.         $errors $this->get('validator')->validate($user);
  342.         if (count($errors) > 0) {
  343.             return new JsonResponse(['success' => false'message' => (string)$errors], Response::HTTP_BAD_REQUEST);
  344.         }
  345.         if ($user->getPlainPassword()) {
  346.             $user->setPassword(
  347.                 $this->userPasswordHasher->hashPassword(
  348.                     $user,
  349.                     $user->getPlainPassword()
  350.                 )
  351.             );
  352.         }
  353.         $this->entityManager->flush();
  354.         return new JsonResponse(['success' => true'token' => $user->getToken()]);
  355.     }
  356.     /**
  357.      * получить токен remember me и записать его в куки
  358.      * /json/set_cookies_post/process
  359.      *
  360.      * 2ae2077f541027486070ab8c446f5149
  361.      * /json/set_cookies_post/process  post $username=vpechenikin $hash=e2685bdfdd2042ac3bafa0aec1ae6726
  362.      * @param Request $request
  363.      * @return JsonResponse|Response
  364.      * @throws Exception
  365.      */
  366.     public function setCookiePostAction(Request $request)
  367.     {
  368.         $data json_decode($request->getContent(), true);
  369.         $username $data['username'];
  370.         if (!SecuriHelper::checkHashPortal($data['hash'])) {
  371.             return new JsonResponse(['success' => false]);
  372.         }
  373.         $user $this->getDoctrine()->getRepository("WebBundle:User")->findOneBy(["username" => $username]);
  374. //        if ($user) {
  375. //            $expires = time() + 600;
  376. //            $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword());
  377. //            $name = "REMEMBERME";
  378. //
  379. //            CookieHelper::set($name, $value);
  380. //            $response = new Response();
  381. //            $response->headers->set('Access-Control-Allow-Origin', '*');
  382. //
  383. //            return $response; //new JsonResponse('success');
  384. //        }
  385.         return new JsonResponse(['success' => false]);
  386.     }
  387.     /**
  388.      * @param $class
  389.      * @param $username
  390.      * @param $expires
  391.      * @param $password
  392.      * @return string
  393.      */
  394.     protected function generateCookieValue($class$username$expires$password): string
  395.     {
  396.         return $this->encodeCookie([
  397.             $class,
  398.             base64_encode($username),
  399.             $expires,
  400.             $this->generateCookieHash($class$username$expires$password),
  401.         ]);
  402.     }
  403.     /**
  404.      * @param $class
  405.      * @param $username
  406.      * @param $expires
  407.      * @param $password
  408.      * @return string
  409.      */
  410.     protected function generateCookieHash($class$username$expires$password): string
  411.     {
  412.         $key $this->container->getParameter('secret');
  413.         return hash_hmac('sha256'$class $username $expires $password$key);
  414.     }
  415.     /**
  416.      * @param array $cookieParts
  417.      * @return string
  418.      */
  419.     protected function encodeCookie(array $cookieParts): string
  420.     {
  421.         foreach ($cookieParts as $cookiePart) {
  422.             if (false !== strpos($cookiePartself::COOKIE_DELIMITER)) {
  423.                 throw new InvalidArgumentException(
  424.                     sprintf(
  425.                         '$cookieParts should not contain the cookie delimiter "%s"',
  426.                         self::COOKIE_DELIMITER
  427.                     )
  428.                 );
  429.             }
  430.         }
  431.         return base64_encode(implode(self::COOKIE_DELIMITER$cookieParts));
  432.     }
  433.     /**
  434.      * получить токен remember me и записать его в куки
  435.      * /json/set_cookies_get/process
  436.      *
  437.      * 93579df44754b1b5e91a16c36b252dc8
  438.      * /json/set_cookies_get/process?username=vpechenikin&hash=e2685bdfdd2042ac3bafa0aec1ae6726
  439.      * $username, $hash
  440.      * @param Request $request
  441.      * @return Response
  442.      */
  443.     public function setCookieGetAction(Request $request): Response
  444.     {
  445.         $username $request->query->get('username'null);
  446.         $hash $request->query->get('hash'null);
  447.         $callback $request->query->get('callback'null);
  448.         $callback $callback $callback ' ({})' 'angular.callbacks._0 ({})';
  449.         $response = new Response();
  450.         $response->headers->set('Access-Control-Allow-Origin''*');
  451.         $response->setContent($callback);
  452.         if (!$this->checkSum($username$hash)) {
  453.             return $response;
  454.         }
  455.         $user $this->getDoctrine()->getRepository("WebBundle:User")->findOneBy(["username" => $username]);
  456. //        if ($user) {
  457. //            $expires = time() + (int) TimeConstant::DAY;
  458. //            $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword());
  459. //            $domain = '.' . $request->getHost(); // $this->container->getParameter('domain');
  460. //            $path = '/';
  461. //            $name = "REMEMBERME";
  462. //            $cookie = new Cookie($name, $value, $expires, $path, $domain, false, true);
  463. //            $response->headers->setCookie($cookie);
  464. //        }
  465.         return $response;
  466.     }
  467.     /**
  468.      * @param $username
  469.      * @param $code
  470.      * @return bool
  471.      */
  472.     public function checkSum($username$code): bool
  473.     {
  474. //        $_d = date("Y.m.d");
  475. //        $hash = md5($username . SecuriHelper::getSalt() . $_d);
  476.         return SecuriHelper::checkHashPortal($code$username);
  477.     }
  478. }