app/Plugin/ApgEnhanceSecurityOfAdmin/Event.php line 137

Open in your IDE?
  1. <?php
  2. namespace Plugin\ApgEnhanceSecurityOfAdmin;
  3. use Doctrine\ORM\EntityManager;
  4. use Doctrine\ORM\EntityManagerInterface;
  5. use Eccube\Application;
  6. use Eccube\Common\EccubeConfig;
  7. use Eccube\Entity\Master\Work;
  8. use Eccube\Entity\Member;
  9. use Eccube\Event\EccubeEvents;
  10. use Eccube\Event\EventArgs;
  11. use Eccube\Event\TemplateEvent;
  12. use Eccube\Repository\MemberRepository;
  13. use Eccube\Request\Context;
  14. use Plugin\ApgEnhanceSecurityOfAdmin\Domain\OnetimeType;
  15. use Plugin\ApgEnhanceSecurityOfAdmin\Entity\ApgLoginHistory;
  16. use Plugin\ApgEnhanceSecurityOfAdmin\Entity\ApgOperationHistory;
  17. use Plugin\ApgEnhanceSecurityOfAdmin\Entity\Config;
  18. use Plugin\ApgEnhanceSecurityOfAdmin\Repository\ConfigRepository;
  19. use Plugin\ApgEnhanceSecurityOfAdmin\Repository\LoginHistoryRepository;
  20. use Plugin\ApgEnhanceSecurityOfAdmin\Repository\OperationHistoryRepository;
  21. use Plugin\ApgEnhanceSecurityOfAdmin\Service\MailService;
  22. use Plugin\ApgEnhanceSecurityOfAdmin\Service\TwoFactorService;
  23. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  24. use Symfony\Component\HttpFoundation\RedirectResponse;
  25. use Symfony\Component\HttpFoundation\Request;
  26. use Symfony\Component\HttpFoundation\RequestStack;
  27. use Symfony\Component\HttpKernel\Event\GetResponseEvent;
  28. use Symfony\Component\HttpKernel\KernelEvents;
  29. use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
  30. use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
  31. use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
  32. use Symfony\Component\Security\Core\AuthenticationEvents;
  33. use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
  34. use Symfony\Component\Security\Core\Exception\LockedException;
  35. use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
  36. use Symfony\Component\Security\Http\SecurityEvents;
  37. class Event implements EventSubscriberInterface
  38. {
  39.     /**
  40.      * @var EntityManager
  41.      */
  42.     protected $em;
  43.     /** @var Context $requestContext */
  44.     protected $requestContext;
  45.     /** @var EccubeConfig $eccubeConfig */
  46.     protected $eccubeConfig;
  47.     /** @var RequestStack $requestStack */
  48.     protected $requestStack;
  49.     /** @var ConfigRepository $configRepository */
  50.     protected $configRepository;
  51.     /** @var MemberRepository $memberRepository */
  52.     protected $memberRepository;
  53.     /** @var LoginHistoryRepository */
  54.     protected $loginHistoryRepository;
  55.     /** @var OperationHistoryRepository */
  56.     protected $operationHistoryRepository;
  57.     /** @var UrlGeneratorInterface $generator */
  58.     protected $generator;
  59.     /** @var TokenStorageInterface $tokenStorage */
  60.     protected $tokenStorage;
  61.     /** @var MailService */
  62.     protected $mailService;
  63.     /** @var TwoFactorService */
  64.     protected $twoFactorService;
  65.     /** @var \Twig_Environment */
  66.     protected $twig;
  67.     const TEMPLATE_NAMESPACE '@ApgEnhanceSecurityOfAdmin';
  68.     const SESSION_ONETIME_PASSWORD_CODE "apg_onetime_password_code";
  69.     const SESSION_ONETIME_VALID "apg_onetime_valid";
  70.     public function __construct(
  71.         EntityManagerInterface $em
  72.         Context $context
  73.         , \Twig_Environment $twig
  74.         RequestStack $requestStack
  75.         EccubeConfig $eccubeConfig
  76.         ConfigRepository $configRepository
  77.         LoginHistoryRepository $loginHistoryRepository
  78.         MemberRepository $memberRepository
  79.         OperationHistoryRepository $operationRepository
  80.         UrlGeneratorInterface $generator
  81.         TokenStorageInterface $tokenStorage
  82.         MailService $mailService
  83.         TwoFactorService $twoFactorService
  84.     )
  85.     {
  86.         $this->em $em;
  87.         $this->twig $twig;
  88.         $this->requestStack $requestStack;
  89.         $this->eccubeConfig $eccubeConfig;
  90.         $this->requestContext $context;
  91.         $this->configRepository $configRepository;
  92.         $this->loginHistoryRepository $loginHistoryRepository;
  93.         $this->memberRepository $memberRepository;
  94.         $this->operationHistoryRepository $operationRepository;
  95.         $this->generator $generator;
  96.         $this->tokenStorage $tokenStorage;
  97.         $this->mailService $mailService;
  98.         $this->twoFactorService $twoFactorService;
  99.     }
  100.     /**
  101.      * @return array
  102.      */
  103.     public static function getSubscribedEvents()
  104.     {
  105.         return [
  106.             SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin',
  107.             AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
  108.             KernelEvents::REQUEST => 'onKernelRequest',
  109.             EccubeEvents::ADMIN_ADMIM_INDEX_COMPLETE => 'onAdminAdminIndexComplete',
  110.             EccubeEvents::ADMIN_SETTING_SYSTEM_MEMBER_EDIT_INITIALIZE => 'onAdminSettingSystemMemberEditInitialize',
  111.             EccubeEvents::ADMIN_ADMIN_CHANGE_PASSWORD_COMPLETE => 'onAdminAdminChangePasswordComplete',
  112.             '@admin/Setting/System/member.twig' => 'onRenderAdminSettingSystemMember',
  113.         ];
  114.     }
  115.     public function onKernelRequest(GetResponseEvent $event)
  116.     {
  117.         $app Application::getInstance();
  118. //        $security = $app->getParentContainer()->get('security');
  119.         if (!$event->isMasterRequest()) {
  120.             return;
  121.         }
  122.         if ($this->requestContext->isAdmin()) {
  123.             $config $this->configRepository->getOrNew();
  124.             /** @var Request $request */
  125.             $request $event->getRequest();
  126.             $requestUrl $request->getRequestUri();
  127.             $loginUrl $this->generator->generate('admin_login');
  128.             $onetimeUrl $this->generator->generate('apg_enhance_security_of_admin_onetime');
  129.             /** @var Member $user */
  130.             $member $this->requestContext->getCurrentUser();
  131.             if (!empty($member)) {
  132.                 if (!empty($config->getUseOperationHistory())) {
  133.                     // 操作ログの保存
  134.                     $operationHistory = new ApgOperationHistory();
  135.                     $operationHistory->setMetaFromServer($request);
  136.                     $operationHistory->setLoginId($member->getLoginId());
  137.                     $operationHistory->setMemberId($member->getId());
  138.                     $operationHistory->setMemberName($member->getName());
  139.                     $operationHistory->setAction($request->getMethod());
  140.                     $this->operationHistoryRepository->save($operationHistory);
  141.                     $this->em->flush();
  142.                 }
  143.             }
  144.             if (!empty($config->getUseOnetime())) {
  145.                 if (
  146.                     strpos($requestUrl$loginUrl) !== 0
  147.                     && strpos($requestUrl$onetimeUrl) !== 0
  148.                 ) {
  149.                     if ($request->getSession()->has(self::SESSION_ONETIME_VALID)) {
  150.                         $pass $request->getSession()->get(self::SESSION_ONETIME_VALID);
  151.                         if (empty($pass)) {
  152.                             $event->setResponse(new RedirectResponse($onetimeUrl));
  153.                         }
  154.                     } else {
  155.                         // ログアウト処理
  156.                         $this->tokenStorage->setToken(null);
  157.                         $request->getSession()->clear();
  158.                         $event->setResponse(new RedirectResponse($loginUrl));
  159.                     }
  160.                 }
  161.             }
  162.         }
  163.     }
  164.     public function onInteractiveLogin(InteractiveLoginEvent $event)
  165.     {
  166.         /** @var Request $request */
  167.         $request $event->getRequest();
  168.         $user $event
  169.             ->getAuthenticationToken()
  170.             ->getUser();
  171.         if ($user instanceof Member) {
  172.             $config $this->configRepository->getOrNew();
  173.             Request::setTrustedProxies(
  174.                 ['192.0.0.1''10.0.0.0/8'],
  175.                 // trust *all* "X-Forwarded-*" headers
  176.                 Request::HEADER_X_FORWARDED_ALL
  177. //            // or, if your proxy instead uses the "Forwarded" header
  178. //             Request::HEADER_FORWARDED
  179.             );
  180.             // @todo ロードバランサー対応(将来?)
  181. //            Request::setTrustedProxies(
  182. //            // trust *all* requests
  183. //                ['127.0.0.1', $request->server->get('REMOTE_ADDR')],
  184. //
  185. //                // if you're using ELB, otherwise use a constant from above
  186. //                Request::HEADER_X_FORWARDED_AWS_ELB
  187. //            );
  188.             $loginHistory $this->loginHistoryRepository->createLoginHistory($request$user);
  189.             if ($loginId $request->get('login_id'null)) {
  190.                 $loginHistory->setInputLoginId($loginId);
  191.             }
  192.             if ($config->isLocked($user)) {
  193.                 // ロックカウントをオーバーしていたら、ログインに成功していても弾く
  194.                 $locked true;
  195.                 $loginHistory->setLocked();
  196.             } else {
  197.                 $locked false;
  198.                 // 2段階認証用のセッションを保存
  199.                 $useOnetime false;
  200.                 if (!empty($config->getUseOnetime())) {
  201.                     if (!empty($user->getUseOnetime())) {
  202.                         $useOnetime true;
  203.                     }
  204.                 }
  205.                 $this->updateFailureCount($config$user0);
  206.                 if (!$useOnetime) {
  207.                     $loginHistory->setSuccess();
  208.                     $request->getSession()->set(self::SESSION_ONETIME_VALIDtrue);
  209.                 } else {
  210.                     $loginHistory->setSuccess();
  211.                     $request->getSession()->set(self::SESSION_ONETIME_VALIDfalse);
  212.                     $targetDate = new \DateTime();
  213.                     $expireTime $config->getOnetimeExpireTime();
  214.                     if (empty($expireTime) || $expireTime 0) {
  215.                         $expireTime 10;   // デフォルト10分
  216.                     }
  217.                     if ($config->getOnetimeType() === OnetimeType::EMAIL) {
  218.                         $aryOnetime $this->twoFactorService->createOnetime($targetDate$expireTime);
  219.                         $this->mailService->sendOnetimePasswordMail($user$aryOnetime['onetime'], $aryOnetime['expireDate']);
  220.                         $request->getSession()->set(self::SESSION_ONETIME_PASSWORD_CODE$aryOnetime);
  221.                     }
  222.                 }
  223.             }
  224.             if (!empty($config->getUseLoginHistory())) {
  225.                 $this->loginHistoryRepository->save($loginHistory);
  226.             }
  227.             $this->em->flush();
  228.             if ($locked) {
  229.                 throw new LockedException();
  230.             }
  231.             if (!$useOnetime) { // 2段階認証利用外のときだけ
  232.                 if (!empty($config->getUseLoginEmail())) {  // ログインメールが有効のときだけ
  233.                     $loginHistory $this->loginHistoryRepository->createLoginHistory($request$user);
  234.                     $this->mailService->sendLoginMail($user$loginHistory);
  235.                 }
  236.             }
  237.             $this->em->flush();
  238.         }
  239.     }
  240.     public function onAuthenticationFailure(AuthenticationFailureEvent $event)
  241.     {
  242.         /** @var UsernamePasswordToken $token */
  243.         $token $event->getAuthenticationToken();
  244.         if ($token->getProviderKey() === 'admin') {
  245.             $locked false;
  246.             $request $this->requestStack->getCurrentRequest();
  247.             $loginHistory = new ApgLoginHistory();
  248.             $loginHistory->setMetaFromServer($request);
  249.             $loginHistory->setFailure();
  250.             if ($loginId $request->get('login_id'null)) {
  251.                 $loginHistory->setInputLoginId($loginId);
  252.                 $user $this->getMember($loginId);
  253.                 if (!empty($user)) {
  254.                     $loginHistory->setMemberId($user->getId());
  255.                     $loginHistory->setMemberName($user->getName());
  256.                     $loginHistory->setMemberEmail($user->getNotifyEmail());
  257.                     $config $this->configRepository->getOrNew();
  258.                     if ($config->isLocked($user)) {
  259.                         // ロックカウントをオーバーしていたら、ロック用のメッセージを出力
  260.                         $locked true;
  261.                         $loginHistory->setLocked();
  262.                     } else {
  263.                         $user $this->updateFailureCount($config$user1);
  264.                         if ($config->isLocked($user)) {
  265.                             // 最初にロックされた段階でメールを飛ばす
  266.                             $this->mailService->sendAccountLockedMail($user$loginHistory);
  267.                         }
  268.                     }
  269.                 }
  270.             }
  271.             $this->loginHistoryRepository->save($loginHistory);
  272.             $this->em->flush();
  273.             if ($locked) {
  274.                 throw new LockedException();
  275.             }
  276.         }
  277.     }
  278.     private function getMember($loginId)
  279.     {
  280.         /** @var Member $member */
  281.         $member $this->memberRepository->findOneBy(['login_id' => $loginId'Work' => Work::ACTIVE]);
  282.         return $member;
  283.     }
  284.     /**
  285.      * @param Member $member
  286.      * @param int $count
  287.      * @return Member
  288.      */
  289.     private function updateFailureCount(Config $configMember $member$count 0)
  290.     {
  291.         if ($count 0) {
  292.             $storedCount $member->getLoginFailureCount();
  293.             if (empty($storedCount) || $config->isFailureExpired($member)) {
  294.                 // 最終ログイン失敗日時が有効期限切れの場合は、失敗回数を0に戻す
  295.                 $storedCount 0;
  296.             }
  297.             $count $storedCount $count;
  298.             $member->setLoginFailureLastDate(new \DateTime());
  299.         } else {
  300.             $count 0;
  301.             $member->setLoginFailureLastDate(null);
  302.         }
  303.         $member->setLoginFailureCount($count);
  304.         $this->em->persist($member);
  305.         return $member;
  306.     }
  307.     public function onAdminAdminIndexComplete(EventArgs $event)
  308.     {
  309.         /** @var Request $request */
  310.         $request $event->getRequest();
  311.         /** @var Member $user */
  312.         $member $this->requestContext->getCurrentUser();
  313.         if (empty($member->getNotifyEmail()) && !$request->isXmlHttpRequest()) {
  314.             $request->getSession()->getFlashBag()->add('eccube.admin.danger''メールアドレスが登録されていません。[登録情報]の[アカウント編集]よりメールアドレスを登録してください。');
  315.         }
  316.     }
  317.     public function onAdminSettingSystemMemberEditInitialize(EventArgs $event)
  318.     {
  319.     }
  320.     public function onAdminAdminChangePasswordComplete(EventArgs $event)
  321.     {
  322.         $config $this->configRepository->get();
  323.         if (!empty($config->getUseEditEmail())) {
  324.             /** @var Request $request */
  325.             $request $event->getRequest();
  326.             /** @var Member $member */
  327.             $member $event->getArgument('Member');
  328.             $operationHistory = new ApgOperationHistory();
  329.             $operationHistory->setMetaFromServer($request);
  330.             $operationHistory->setLoginId($member->getLoginId());
  331.             $this->mailService->sendEditPasswordMail($member$operationHistory);
  332.         }
  333.     }
  334.     public function onRenderAdminSettingSystemMember(TemplateEvent $event)
  335.     {
  336.         $source $event->getSource();
  337.         // data
  338.         $parameters $event->getParameters();
  339.         // setting
  340.         $loader $this->twig->getLoader();
  341.         // header
  342.         $pattern '|<th class="border-top-0 pt-2 pb-2 text-center"></th>|s';
  343.         $addRow '<th class="border-top-0 pt-2 pb-2 text-center">メールアドレス</th><th class="border-top-0 pt-2 pb-2 text-center">2段階認証</th>';
  344.         if (preg_match($pattern$source$matchesPREG_OFFSET_CAPTURE)) {
  345.             $replacement $addRow $matches[0][0];
  346.             $source preg_replace($pattern$replacement$source);
  347.         }
  348.         // data
  349.         $pattern '|{{ Member.Work.name }}(.*?)</td>|s';
  350.         $addRow $this->twig->getLoader()->getSourceContext(self::TEMPLATE_NAMESPACE '/admin/setting_system_member.twig')->getCode();
  351.         if (preg_match($pattern$source$matchesPREG_OFFSET_CAPTURE)) {
  352.             $replacement $matches[0][0] . $addRow;
  353.             $source preg_replace($pattern$replacement$source);
  354.         }
  355.         $event->setSource($source);
  356.         $event->setParameters($parameters);
  357.     }
  358. }