src/WebBundle/Controller/UserController.php line 99

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