<?php
namespace WebBundle\Controller;
use Doctrine\DBAL\Exception\UniqueConstraintViolationException;
use Doctrine\ORM\EntityManagerInterface;
use Exception;
use FlexApp\Constant\TimeConstant;
use FlexApp\Exceptions\PortalApiException;
use FlexApp\Helper\SecuriHelper;
use FlexApp\Security\LoginFormAuthenticator;
use FlexApp\Service\Canonicalizer;
use FlexApp\Service\ManualReg\UserLogger;
use InvalidArgumentException;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Http\Authentication\UserAuthenticatorInterface;
use WebBundle\Entity\OrderAddress;
use WebBundle\Entity\User;
use WebBundle\Helper\App;
use WebBundle\Helper\CookieHelper;
use WebBundle\Helper\SocialLoginHelper;
use WebBundle\Helper\UserHelper;
use WebBundle\Repository\UserRepository;
use WebBundle\Service\OrderService;
use WebBundle\Service\RegistrationService;
use WebBundle\Service\SocialLoginService;
use function Symfony\Component\String\u;
/**
* User controller.
*/
class UserController extends ExtendedController
{
const URL_TILE_TEST_AUTH = 'https://tile.expert/json/set_cookies_test/process';
const COOKIE_DELIMITER = ':';
/** @required */
public UserAuthenticatorInterface $userAuthenticator;
/** @required */
public LoginFormAuthenticator $loginFormAuthenticator;
/** @required */
public RequestStack $requestStack;
/** @required */
public EntityManagerInterface $entityManager;
/** @required */
public UserPasswordHasherInterface $userPasswordHasher;
/** @required */
public UserLogger $userLogger;
/** @required */
public UserRepository $userRepository;
/** @required */
public Canonicalizer $canonicalizer;
protected OrderService $orderService;
private array $messages = [
'Bad credentials.' => 'user_login_bad'
// 'Invalid CSRF token.' => 'user_login_bad'
];
private array $patterns = [
'login' => UserHelper::VALID_SYMBOL_LOGIN, // "/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[_!@#$%&])(?=.{4,})/i",
'password' => UserHelper::VALID_SYMBOL_PASSWORD, // "/^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[_!@#$%&])(?=.{4,})/i",
'email' => UserHelper::VALID_SYMBOL_EMAIL, // "/^([a-zA-Z0-9_\.-]+\@[\da-zA-Z\.-]+\.[a-zA-Z\.]{2,6})$/i",
];
/**
* @throws Exception
*/
public function loginWithoutLocaleAction(): RedirectResponse
{
return new RedirectResponse(App::generateUrl('app_login'));
}
/**
* @throws Exception
*/
public function logoutWithoutLocaleAction(): RedirectResponse
{
return new RedirectResponse(App::generateUrl('app_logout'));
}
/**
* @param Request $request
* @return Response
* @throws Exception
*/
public function loginAction(Request $request)
{
/** @var Session $session */
$session = $request->getSession();
$user = App::getCurUser();
if ($user) {
if (!empty($_SERVER['HTTP_REFERER'])) {
$referer = $_SERVER['HTTP_REFERER'];
return new RedirectResponse($referer);
}
return $this->redirectToRoute('app_home', ['_locale' => App::getCurLocale()]);
}
$redirectUrl = $request->query->get('redirect_url');
if ($redirectUrl) {
$request->getSession()->set('login_referer', $redirectUrl);
}
$authenticationUtils = $this->get('security.authentication_utils');
if (!$request->getSession()->has('_authenticationError')) {
$request->getSession()->set('_authenticationError', 0);
}
// получить ошибки логина, если таковые имеются
if ($request->attributes->has(Security::AUTHENTICATION_ERROR)) {
$error = $request->attributes->get(Security::AUTHENTICATION_ERROR);
} else {
$error = $session->get(Security::AUTHENTICATION_ERROR);
//Bad credentials.
}
$errorMessageBad = $error && isset($this->messages[$error->getMessage()])
? $this->get('translator')->trans($this->messages[$error->getMessage()])
: null;
if ($error && !$errorMessageBad) {
$errorMessageBad = $error->getMessage();
}
// Считаем кол-во попыток
$authError = $authenticationUtils->getLastAuthenticationError();
if ($authError !== null) {
if ($request->getSession()->has('_authenticationError')) {
// Сохраняем предыдущий результат
$prevError = $request->getSession()->get('_authenticationError');
// Удаляем предыдущий результат
$request->getSession()->remove('_authenticationError');
// Сохраняем новый
$request->getSession()->set('_authenticationError', $prevError + 1);
}
}
// Проверка на кол-во ошибок ввода
$authenticationError = $request->getSession()->has('_authenticationError') &&
$request->getSession()->get('_authenticationError') >= 5;
if ($authenticationError) {
if (!$request->getSession()->has('_authenticationWait')) {
$request->getSession()->set('_authenticationWait', time() + 10 * 60);
}
}
if ($request->getSession()->has('_authenticationWait')) {
if ($request->getSession()->get('_authenticationWait') <= time()) {
$request->getSession()->remove('_authenticationWait');
$request->getSession()->remove('_authenticationTimeWait');
$request->getSession()->set('_authenticationError', 0);
$authenticationError = false;
} else {
$request->getSession()->set('_authenticationTimeWait', $request->getSession()->get('_authenticationWait') - time());
}
}
$csrfToken = $this->get('security.csrf.token_manager')->getToken('authenticate')->getValue();
/**
* @var $referer
* Выбирает реферер ссылку из заголовка и помещает в сессионную переменную, для использования в логине,
* если мы ранее не находиль на странице авторизации /{_locale}/login
*/
$logger = App::getContainer()->get('logger_public');
$referer = $request->headers->get('referer');
$logger->info('получаем referer ' . $referer);
$refererSession = $request->getSession()->get('login_referer');
$logger->info('получаем refererSession ' . $refererSession);
if (strpos($refererSession, 'css') !== false) {
$request->getSession()->remove('login_referer');
$logger->error('remove login_referer ' . $refererSession);
}
if ($referer) {
if (strpos($referer, 'login') === false && strpos($referer, 'css') === false) {
$request->getSession()->set('login_referer', $referer);
$logger->info('set login_referer login ' . $referer);
}
} else {
$logger->error('set login_referer no ' . $referer);
}
$logger->info('check login ' . strpos($referer, 'login') . ' ' . $referer);
$logger->info('check css ' . strpos($referer, 'css') . ' ' . $referer);
$socialLogClent = new SocialLoginHelper($this->container);
return $this->render('@Web/User/login.html.twig', [
// имя, введённое пользователем в последний раз
'last_username' => $session->get(Security::LAST_USERNAME),
'csrf_token' => $csrfToken,
'token' => UserHelper::getInstance()->getToken(),
'authenticationError' => $authenticationError,
'authenticationTimeWait' => $request->getSession()->get('_authenticationTimeWait', null),
'error' => $error,
'errorMessageBad' => $errorMessageBad,
'vkUrl' => $socialLogClent->getVkUrl(),
'instagramUrl' => $socialLogClent->getInstagramUrl(),
'googleUrl' => $socialLogClent->googleClient()->createAuthUrl(),
'facebookUrl' => $socialLogClent->getFacebookUrl(),
'locale' => App::getCurLocale()
]);
}
/**
* @param Request $request
* @return RedirectResponse|Response
* @throws Exception
*/
function registrationAction(Request $request)
{
$referer = $this->get('request_stack')->getCurrentRequest()->headers->get('referer');
$session = $this->get('session');
if (strpos($referer, 'chat') !== false) {
$session->set('target_url_for_chat', $this->generateUrl('app_chatb_chatbpage'));
}
/** @var RegistrationService $registrationService */
$registrationService = $this->get('app.registration');
/** @var User $user */
try {
$user = $registrationService->register($request);
} catch (UniqueConstraintViolationException $e) {
return $this->redirectToRoute('app_logout');
}
$error_message = null;
// Если получилось зарегистрировать пользователя
if ($user) {
// То авторизуем его
$this->userAuthenticator->authenticateUser($user, $this->loginFormAuthenticator, $this->requestStack->getCurrentRequest());
// $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
// $this->get('security.token_storage')->setToken($token);
// $session->set('_security_main', serialize($token));
// И редиректим на домашнюю страницу
$url = $this->generateUrl('app_home');
// Если перешел по ссылке из чата, то редиректим обратно в чат, чтобы не потерять клиента,
// и чтобы не заставлять его переходить с главной страницы сначала в контакты, а оттуда в чат.
if ($session->has('target_url_for_chat')) {
$url = $session->get('target_url_for_chat');
$session->remove('target_url_for_chat');
}
if ($session->has('login_referer')) {
$url = $session->get('login_referer');
$this->orderService = $this->get('app.service.order');
$this->orderService->checkOrderByLink($url, $user);
}
return new RedirectResponse($url);
}
$socialLogClent = new SocialLoginHelper($this->container);
$response = $this->render(
'@Web/User/registration.html.twig',
[
'ip' => $request->getClientIp(),
'token' => UserHelper::getInstance()->getToken(),
'form' => $registrationService->getRegistrationForm()->createView(),
'vkUrl' => $socialLogClent->getVkUrl(),
'instagramUrl' => $socialLogClent->getInstagramUrl(),
'googleUrl' => $socialLogClent->googleClient()->createAuthUrl(),
'facebookUrl' => $socialLogClent->getFacebookUrl(),
'locale' => App::getCurLocale()
]
);
return $response;
}
/**
* Авторизация через соц.сети
* @param Request $request
* @return Response
* @throws Exception
*/
public function socialLoginAction(Request $request): Response
{
$successUrl = $this->generateUrl('app_home');
/** @var SocialLoginService $socialLoginService */
$socialLoginService = $this->get("app.social_login");
if ($request->get('formEmail')) {
$result = $socialLoginService->socialLogin(0, 0, $request->request->all());
if (gettype($result) == 'array' && isset($result['confirmEmail'])) {
return $this->render('@Web/User/slogin-email.html.twig', array_merge($result['confirmEmail'], ['locale' => App::getCurLocale(), 'confirmEmail' => true]));
}
} elseif ($request->get('confirmEmail')) {
$data = $request->request->all();
$result = $socialLoginService->confirmEmailByPassword($data['email'], $data['password'], $data);
if (!$result) {
return $this->render('@Web/User/slogin-email.html.twig', array_merge($data, ['locale' => App::getCurLocale(), 'confirmEmail' => true, 'error' => true]));
}
} else {
$network = $request->get("network");
$networkCode = $request->get("code");
$result = $socialLoginService->socialLogin($networkCode, $network);
if (isset($result['formEmail'])) {
return $this->render('@Web/User/slogin-email.html.twig', array_merge($result['formEmail'], ['locale' => App::getCurLocale()]));
} elseif (isset($result['confirmEmail'])) {
return $this->render('@Web/User/slogin-email.html.twig', array_merge($result['confirmEmail'], ['locale' => App::getCurLocale(), 'confirmEmail' => true]));
}
}
return $this->redirect(
$result
? $successUrl
: App::generateUrl('app_login', ['_locale' => App::getCurLocale()])
);
}
/**
* Добавление - изменение пользователя с портала
* согласовано поле идентификатор - username
*
* @param Request $request
* @return JsonResponse
* @throws PortalApiException|Exception
*/
public function addAction(Request $request): JsonResponse
{
$this->userLogger->log('Запрос с портала: ' . $request->getContent());
$data = json_decode($request->getContent(), true);
$docum = $data['document'];
if (!u($docum['emailCanonical'])->containsAny(['@tile.expert', '@treto.ru'])) {
return new JsonResponse(['success' => false, 'message' => "Ошибка валидации: emailCanonical '{$docum['emailCanonical']}' не содержит @tile.expert или @treto.ru"], Response::HTTP_BAD_REQUEST);
}
// Договорились идентифицировать пользователя по единственному полю - "username": https://te2.remote.team/discus/13D76A4A-5404-BE01-BFF1-DC000ED813BE
$user = $this->userRepository->findOneBy(['username' => $this->canonicalizer->canonicalize($docum['username'])]);
$isNewUser = false;
if (!$user) {
$isNewUser = true;
$user = new User();
// генерируем только при создании пользователя
$user->setToken(md5(date('Y-m-dH:i:s')));
$orderAddress = new OrderAddress();
$orderAddress->setIsMainRecipient(true);
$orderAddress->setToken($user->getToken());
$user->addOrderAddress($orderAddress);
$this->entityManager->persist($user);
}
if ($docum['unid'] ?? '') {
$anotherUserWithTheSameUnid = $this->userRepository->findOneByUnid($docum['unid'] ?? '');
if ($anotherUserWithTheSameUnid && $anotherUserWithTheSameUnid->getId() !== $user->getId()) {
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);
}
$user->setUnid($docum['unid']);
} else {
if ($isNewUser) {
return new JsonResponse(['success' => false, 'message' => "Поле unid является обязательным для новых пользователей."]);
}
}
$user->setEmail($docum['emailCanonical']);
$user->setUsername($this->canonicalizer->canonicalize($docum['username']));
$user->setAlias($docum['portalData']['alias']);
if ($docum['portalData']['plainPassword'] ?? null) {
$user->setPlainPassword($docum['portalData']['plainPassword']);
} else {
if ($isNewUser) {
return new JsonResponse(['success' => false, 'message' => "Пользователь username {$user->getUsername()} не найден в базе данных. Для новых пользователей поле plainPassword является обязательным."]);
} else {
$user->setPlainPassword(null);
}
}
$errors = $this->get('validator')->validate($user);
if (count($errors) > 0) {
return new JsonResponse(['success' => false, 'message' => (string)$errors], Response::HTTP_BAD_REQUEST);
}
if ($user->getPlainPassword()) {
$user->setPassword(
$this->userPasswordHasher->hashPassword(
$user,
$user->getPlainPassword()
)
);
}
$this->entityManager->flush();
return new JsonResponse(['success' => true, 'token' => $user->getToken()]);
}
/**
* получить токен remember me и записать его в куки
* /json/set_cookies_post/process
*
* 2ae2077f541027486070ab8c446f5149
* /json/set_cookies_post/process post $username=vpechenikin $hash=e2685bdfdd2042ac3bafa0aec1ae6726
* @param Request $request
* @return JsonResponse|Response
* @throws Exception
*/
public function setCookiePostAction(Request $request)
{
$data = json_decode($request->getContent(), true);
$username = $data['username'];
if (!SecuriHelper::checkHashPortal($data['hash'])) {
return new JsonResponse(['success' => false]);
}
$user = $this->getDoctrine()->getRepository("WebBundle:User")->findOneBy(["username" => $username]);
// if ($user) {
// $expires = time() + 600;
// $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword());
// $name = "REMEMBERME";
//
// CookieHelper::set($name, $value);
// $response = new Response();
// $response->headers->set('Access-Control-Allow-Origin', '*');
//
// return $response; //new JsonResponse('success');
// }
return new JsonResponse(['success' => false]);
}
/**
* @param $class
* @param $username
* @param $expires
* @param $password
* @return string
*/
protected function generateCookieValue($class, $username, $expires, $password): string
{
return $this->encodeCookie([
$class,
base64_encode($username),
$expires,
$this->generateCookieHash($class, $username, $expires, $password),
]);
}
/**
* @param $class
* @param $username
* @param $expires
* @param $password
* @return string
*/
protected function generateCookieHash($class, $username, $expires, $password): string
{
$key = $this->container->getParameter('secret');
return hash_hmac('sha256', $class . $username . $expires . $password, $key);
}
/**
* @param array $cookieParts
* @return string
*/
protected function encodeCookie(array $cookieParts): string
{
foreach ($cookieParts as $cookiePart) {
if (false !== strpos($cookiePart, self::COOKIE_DELIMITER)) {
throw new InvalidArgumentException(
sprintf(
'$cookieParts should not contain the cookie delimiter "%s"',
self::COOKIE_DELIMITER
)
);
}
}
return base64_encode(implode(self::COOKIE_DELIMITER, $cookieParts));
}
/**
* получить токен remember me и записать его в куки
* /json/set_cookies_get/process
*
* 93579df44754b1b5e91a16c36b252dc8
* /json/set_cookies_get/process?username=vpechenikin&hash=e2685bdfdd2042ac3bafa0aec1ae6726
* $username, $hash
* @param Request $request
* @return Response
*/
public function setCookieGetAction(Request $request): Response
{
$username = $request->query->get('username', null);
$hash = $request->query->get('hash', null);
$callback = $request->query->get('callback', null);
$callback = $callback ? $callback . ' ({})' : 'angular.callbacks._0 ({})';
$response = new Response();
$response->headers->set('Access-Control-Allow-Origin', '*');
$response->setContent($callback);
if (!$this->checkSum($username, $hash)) {
return $response;
}
$user = $this->getDoctrine()->getRepository("WebBundle:User")->findOneBy(["username" => $username]);
// if ($user) {
// $expires = time() + (int) TimeConstant::DAY;
// $value = $this->generateCookieValue(get_class($user), $user->getUsername(), $expires, $user->getPassword());
// $domain = '.' . $request->getHost(); // $this->container->getParameter('domain');
// $path = '/';
// $name = "REMEMBERME";
// $cookie = new Cookie($name, $value, $expires, $path, $domain, false, true);
// $response->headers->setCookie($cookie);
// }
return $response;
}
/**
* @param $username
* @param $code
* @return bool
*/
public function checkSum($username, $code): bool
{
// $_d = date("Y.m.d");
// $hash = md5($username . SecuriHelper::getSalt() . $_d);
return SecuriHelper::checkHashPortal($code, $username);
}
}