src/WebBundle/Controller/UserController.php line 127

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