app/Plugin/TwoFactorAuthCustomer42/EventListener/CustomerTwoFactorAuthListener.php line 125

Open in your IDE?
  1. <?php
  2. /*
  3.  * This file is part of EC-CUBE
  4.  *
  5.  * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
  6.  *
  7.  * http://www.ec-cube.co.jp/
  8.  *
  9.  * For the full copyright and license information, please view the LICENSE
  10.  * file that was distributed with this source code.
  11.  */
  12. namespace Plugin\TwoFactorAuthCustomer42\EventListener;
  13. use Eccube\Common\EccubeConfig;
  14. use Eccube\Entity\BaseInfo;
  15. use Eccube\Entity\Customer;
  16. use Eccube\Entity\Master\CustomerStatus;
  17. use Eccube\Repository\BaseInfoRepository;
  18. use Eccube\Request\Context;
  19. use Plugin\TwoFactorAuthCustomer42\Repository\TwoFactorAuthTypeRepository;
  20. use Plugin\TwoFactorAuthCustomer42\Repository\TwoFactorAuthCustomerCookieRepository;
  21. use Plugin\TwoFactorAuthCustomer42\Service\CustomerTwoFactorAuthService;
  22. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  23. use Symfony\Component\HttpFoundation\RedirectResponse;
  24. use Symfony\Component\HttpFoundation\RequestStack;
  25. use Symfony\Component\HttpKernel\Event\ControllerArgumentsEvent;
  26. use Symfony\Component\HttpKernel\KernelEvents;
  27. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  28. use Symfony\Component\Security\Http\Event\LoginSuccessEvent;
  29. use Symfony\Component\Security\Http\Event\LogoutEvent;
  30. use Symfony\Contracts\EventDispatcher\Event;
  31. class CustomerTwoFactorAuthListener implements EventSubscriberInterface
  32. {
  33.     /**
  34.      * @var EccubeConfig
  35.      */
  36.     protected $eccubeConfig;
  37.     /**
  38.      * @var Context
  39.      */
  40.     protected $requestContext;
  41.     /**
  42.      * @var UrlGeneratorInterface
  43.      */
  44.     protected $router;
  45.     /**
  46.      * @var CustomerTwoFactorAuthService
  47.      */
  48.     protected $customerTwoFactorAuthService;
  49.     /**
  50.      * @var TwoFactorAuthTypeRepository
  51.      */
  52.     protected TwoFactorAuthTypeRepository $twoFactorAuthTypeRepository;
  53.     /**
  54.      * @var TwoFactorAuthCustomerCookieRepository
  55.      */
  56.     protected TwoFactorAuthCustomerCookieRepository $twoFactorAuthCustomerCookieRepository;
  57.     /**
  58.      * @var BaseInfo|object|null
  59.      */
  60.     protected $baseInfo;
  61.     /**
  62.      * @var Session
  63.      */
  64.     protected $session;
  65.     /**
  66.      * 通常(ログイン・マイページ)ルート.
  67.      */
  68.     protected $default_routes;
  69.     /**
  70.      * 重要操作ルート.
  71.      */
  72.     protected $include_routes;
  73.     /**
  74.      * @param Context $requestContext
  75.      * @param UrlGeneratorInterface $router
  76.      * @param CustomerTwoFactorAuthService $customerTwoFactorAuthService
  77.      * @param TwoFactorAuthTypeRepository $twoFactorAuthTypeRepository
  78.      * @param TwoFactorAuthCustomerCookieRepository $twoFactorAuthCustomerCookieRepository
  79.      * @param BaseInfoRepository $baseInfoRepository
  80.      * @param RequestStack $requestStack
  81.      */
  82.     public function __construct(
  83.         Context $requestContext,
  84.         UrlGeneratorInterface $router,
  85.         CustomerTwoFactorAuthService $customerTwoFactorAuthService,
  86.         TwoFactorAuthTypeRepository $twoFactorAuthTypeRepository,
  87.         TwoFactorAuthCustomerCookieRepository $twoFactorAuthCustomerCookieRepository,
  88.         BaseInfoRepository $baseInfoRepository,
  89.         RequestStack $requestStack
  90.     ) {
  91.         $this->requestContext $requestContext;
  92.         $this->router $router;
  93.         $this->customerTwoFactorAuthService $customerTwoFactorAuthService;
  94.         $this->baseInfo $baseInfoRepository->find(1);
  95.         $this->twoFactorAuthTypeRepository $twoFactorAuthTypeRepository;
  96.         $this->twoFactorAuthCustomerCookieRepository $twoFactorAuthCustomerCookieRepository;
  97.         $this->session $requestStack->getSession();
  98.         $this->default_routes $this->customerTwoFactorAuthService->getDefaultAuthRoutes();
  99.         $this->include_routes $this->customerTwoFactorAuthService->getIncludeRoutes();
  100.     }
  101.     /**
  102.      * @return array
  103.      */
  104.     public static function getSubscribedEvents(): array
  105.     {
  106.         return [
  107.             KernelEvents::CONTROLLER_ARGUMENTS => ['onKernelController'7],
  108.             LoginSuccessEvent::class => ['onLoginSuccess'],
  109.             LogoutEvent::class => 'logoutEvent',
  110.         ];
  111.     }
  112.     /**
  113.      * リクエスト受信時イベントハンドラ.
  114.      *
  115.      * @param ControllerArgumentsEvent $event
  116.      */
  117.     public function onKernelController(ControllerArgumentsEvent $event)
  118.     {
  119.         if (!$event->isMainRequest()) {
  120.             // サブリクエストの場合、処理なし
  121.             return;
  122.         }
  123.         if ($this->requestContext->isAdmin()) {
  124.             // バックエンドURLの場合、処理なし
  125.             return;
  126.         }
  127.         if (!$this->baseInfo->isTwoFactorAuthUse()) {
  128.             // 2段階認証使用しない場合は処理なし
  129.             return;
  130.         }
  131.         $route $event->getRequest()->attributes->get('_route');
  132.         $uri $event->getRequest()->getRequestUri();
  133.         $Customer $this->requestContext->getCurrentUser();
  134.         if ($Customer instanceof Customer) {
  135.             if ($Customer->getStatus()->getId() !== CustomerStatus::REGULAR) {
  136.                 // ログインしていない場合、処理なし
  137.                 return;
  138.             }
  139.             if (!$this->isDefaultRoute($route$uri) && !$this->isIncludeRoute($route$uri)) {
  140.                 // 重要操作指定ではなく、マイページ系列ではない場合、処理なし
  141.                 return;
  142.             }
  143.             $this->multiFactorAuth($event$Customer$route);
  144.         }
  145.     }
  146.     /**
  147.      * ログイン完了 イベントハンドラ.
  148.      *
  149.      * @param LoginSuccessEvent $event
  150.      *
  151.      * @return RedirectResponse|void
  152.      */
  153.     public function onLoginSuccess(LoginSuccessEvent $event)
  154.     {
  155.         if ($this->requestContext->isAdmin()) {
  156.             // バックエンドURLの場合処理なし
  157.             return;
  158.         }
  159.         if (!$this->baseInfo->isTwoFactorAuthUse()) {
  160.             // 2段階認証使用しない場合は処理なし
  161.             return;
  162.         }
  163.         if ($this->requestContext->getCurrentUser() === null) {
  164.             // ログインしていない場合は処理なし
  165.             return;
  166.         }
  167.         if ($this->requestContext->getCurrentUser()->getTwoFactorAuthType() !== null &&
  168.             $this->requestContext->getCurrentUser()->getTwoFactorAuthType()->isDisabled()) {
  169.             // ユーザーが選択した2段階認証方式は無効になっている場合、ログアウトさせる。
  170.             return new RedirectResponse($this->router->generate('logout'), 302);
  171.         }
  172.         $this->multiFactorAuth(
  173.             $event,
  174.             $this->requestContext->getCurrentUser(),
  175.             $event->getRequest()->attributes->get('_route'));
  176.     }
  177.     /**
  178.      * ログアウトする前に全ての2FA認証クッキーを消す
  179.      *
  180.      * @param LogoutEvent $logoutEvent ログアウトイベント
  181.      *
  182.      * @return void
  183.      */
  184.     public function logoutEvent(LogoutEvent $logoutEvent)
  185.     {
  186.         $Customer $this->requestContext->getCurrentUser();
  187.         if ($Customer instanceof Customer) {
  188.             $this->customerTwoFactorAuthService->clear2AuthCookies($logoutEvent->getRequest(), $logoutEvent->getResponse());
  189.             $this->twoFactorAuthCustomerCookieRepository->deleteByCustomer($Customer);
  190.         }
  191.     }
  192.     /**
  193.      * ルート・URIが個別認証対象かチェック.
  194.      *
  195.      * @param string $route
  196.      * @param string $uri
  197.      *
  198.      * @return bool
  199.      */
  200.     private function isDefaultRoute(string $routestring $uri): bool
  201.     {
  202.         return $this->isTargetRoute($this->default_routes$route$uri);
  203.     }
  204.     /**
  205.      * ルート・URIが対象であるかチェック.
  206.      *
  207.      * @param array $targetRoutes
  208.      * @param string $route
  209.      * @param string $uri
  210.      *
  211.      * @return bool
  212.      */
  213.     private function isTargetRoute(array $targetRoutesstring $routestring $uri): bool
  214.     {
  215.         // ルートで認証
  216.         if (in_array($route$targetRoutes)) {
  217.             return true;
  218.         }
  219.         // URIで認証
  220.         foreach ($targetRoutes as $r) {
  221.             if ($r != '' && $r !== '/' && strpos($uri$r) === 0) {
  222.                 return true;
  223.             }
  224.         }
  225.         return false;
  226.     }
  227.     /**
  228.      * ルート・URIが個別認証対象かチェック.
  229.      *
  230.      * @param string $route
  231.      * @param string $uri
  232.      *
  233.      * @return bool
  234.      */
  235.     private function isIncludeRoute(string $routestring $uri): bool
  236.     {
  237.         return $this->isTargetRoute($this->include_routes$route$uri);
  238.     }
  239.     /**
  240.      * 多要素認証.
  241.      *
  242.      * @param Event $event
  243.      * @param Customer $Customer
  244.      * @param string $route
  245.      *
  246.      * @return mixed
  247.      */
  248.     private function multiFactorAuth($event$Customer$route)
  249.     {
  250.         if (!$this->baseInfo->isTwoFactorAuthUse()) {
  251.             // MFA無効の場合処理なし
  252.             return;
  253.         }
  254.         if (count($this->twoFactorAuthTypeRepository->findBy(['isDisabled' => false])) == 0) {
  255.             // 2段階認証プラグインが有効化されていない場合処理なし
  256.             return;
  257.         }
  258.         // [会員] ログイン時2段階認証状態
  259.         $is_auth $this->customerTwoFactorAuthService->isAuthed($Customer$route);
  260.         if (!$is_auth) {
  261.             log_info('[2段階認証] 実施');
  262.             if ($Customer->getTwoFactorAuthType() === null) {
  263.                 // 2段階認証未設定
  264.                 $this->selectAuthType($event$route);
  265.             } else {
  266.                 // 2段階認証設定済み
  267.                 $this->auth($event$Customer$route);
  268.             }
  269.         }
  270.     }
  271.     /**
  272.      * 多要素認証方式設定画面へリダイレクト.
  273.      *
  274.      * @param Event $event
  275.      * @param string|null $route
  276.      */
  277.     private function selectAuthType($event, ?string $route)
  278.     {
  279.         // [会員] 2段階認証が未設定の場合
  280.         // コールバックURLをセッションへ設定
  281.         $this->setCallbackRoute($route);
  282.         // 2段階認証選択画面へリダイレクト
  283.         $url $this->router->generate('plg_customer_2fa_auth_type_select', [], UrlGeneratorInterface::ABSOLUTE_PATH);
  284.         if ($event instanceof ControllerArgumentsEvent) {
  285.             $event->setController(function () use ($url) {
  286.                 return new RedirectResponse($url302);
  287.             });
  288.         } else {
  289.             $event->setResponse(new RedirectResponse($url302));
  290.         }
  291.     }
  292.     /**
  293.      * コールバックルートをセッションへ設定.
  294.      *
  295.      * @param string|null $route
  296.      */
  297.     private function setCallbackRoute(?string $route)
  298.     {
  299.         if ($route) {
  300.             $this->session->set(CustomerTwoFactorAuthService::SESSION_CALL_BACK_URL$route);
  301.         }
  302.     }
  303.     /**
  304.      * 2段階認証のディスパッチ.
  305.      *
  306.      * @param Event $event
  307.      * @param Customer $Customer
  308.      * @param string|null $route
  309.      */
  310.     private function auth($eventCustomer $Customer, ?string $route)
  311.     {
  312.         // コールバックURLをセッションへ設定
  313.         $this->setCallbackRoute($route);
  314.         // 選択された多要素認証方式で指定されているルートへリダイレクト
  315.         if ($Customer->getTwoFactorAuthType() !== null && $Customer->getTwoFactorAuthType()->isDisabled()) {
  316.             // ユーザーが選択した2段階認証方式は無効になっている場合、ログアウトさせる。
  317.             $event->setController(function () {
  318.                 return new RedirectResponse($this->router->generate('logout'), 302);
  319.             });
  320.             return;
  321.         }
  322.         $url $this->router->generate($Customer->getTwoFactorAuthType()->getRoute());
  323.         if ($event instanceof ControllerArgumentsEvent) {
  324.             $event->setController(function () use ($url) {
  325.                 return new RedirectResponse($url302);
  326.             });
  327.         } else {
  328.             $event->setResponse(new RedirectResponse($url302));
  329.         }
  330.     }
  331. }