custom/plugins/IwvTwoFactorAuthentication/src/Subscriber/BackendLoginSubscriber.php line 81

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace Iwv\IwvTwoFactorAuthentication\Subscriber;
  4. use League\OAuth2\Server\Exception\OAuthServerException;
  5. use Shopware\Core\PlatformRequest;
  6. use Shopware\Core\Framework\Context;
  7. use Shopware\Core\Framework\DataAbstractionLayer\EntityRepositoryInterface;
  8. use Shopware\Core\Framework\DataAbstractionLayer\Search\Criteria;
  9. use Shopware\Core\Framework\DataAbstractionLayer\Search\Filter\EqualsFilter;
  10. use Shopware\Core\System\User\UserEntity;
  11. use Symfony\Component\EventDispatcher\EventSubscriberInterface;
  12. use Symfony\Component\HttpKernel\Event\ResponseEvent;
  13. use Symfony\Component\HttpFoundation\Response;
  14. use Symfony\Component\HttpFoundation\Request;
  15. use Symfony\Component\HttpKernel\KernelEvents;
  16. use Iwv\IwvTwoFactorAuthentication\Service\TwoFactorAdaptors\GoogleAuthenticatorAdaptor;
  17. use Iwv\IwvTwoFactorAuthentication\Service\TwoFactorAdaptors\YubicoAuthenticatorAdaptor;
  18. use Iwv\IwvTwoFactorAuthentication\Service\TwoFactorAdaptors\EmailAuthenticatorAdaptor;
  19. use Iwv\IwvTwoFactorAuthentication\Service\CookieHelperValidator;
  20. use Iwv\IwvTwoFactorAuthentication\Service\SessionHelpers\SessionTokenHelper;
  21. use Exception;
  22. class BackendLoginSubscriber implements EventSubscriberInterface
  23. {
  24.     /**
  25.      * @var EntityRepositoryInterface
  26.      */
  27.     private $userRepository;
  28.     /**
  29.      * @var CookieHelperValidator
  30.      */
  31.     private $cookieHelperValidator;
  32.     /**
  33.      * @var GoogleAuthenticatorAdaptor
  34.      */
  35.     private $googleAuthenticatorAdaptor;
  36.     /**
  37.      * @var YubicoAuthenticatorAdaptor
  38.      */
  39.     private $yubicoAuthenticatorAdaptor;
  40.     /**
  41.      * @var EmailAuthenticatorAdaptor
  42.      */
  43.     private $emailAuthenticatorAdaptor;
  44.     /**
  45.      * @var SessionTokenHelper
  46.      */
  47.     private $sessionTokenHelper;
  48.     public function __construct(
  49.         EntityRepositoryInterface $userRepository,
  50.         CookieHelperValidator $cookieHelperValidator,
  51.         GoogleAuthenticatorAdaptor $googleAuthenticatorAdaptor,
  52.         YubicoAuthenticatorAdaptor $yubicoAuthenticatorAdaptor,
  53.         EmailAuthenticatorAdaptor $emailAuthenticatorAdaptor,
  54.         SessionTokenHelper $sessionTokenHelper
  55.     ) {
  56.         $this->userRepository $userRepository;
  57.         $this->cookieHelperValidator $cookieHelperValidator;
  58.         $this->googleAuthenticatorAdaptor $googleAuthenticatorAdaptor;
  59.         $this->yubicoAuthenticatorAdaptor $yubicoAuthenticatorAdaptor;
  60.         $this->emailAuthenticatorAdaptor $emailAuthenticatorAdaptor;
  61.         $this->sessionTokenHelper $sessionTokenHelper;
  62.     }
  63.     public static function getSubscribedEvents()
  64.     {
  65.         return [
  66.             KernelEvents::RESPONSE => 'onResponse',
  67.         ];
  68.     }
  69.     public function onResponse(ResponseEvent $event): void
  70.     {
  71.         /** @var \Symfony\Component\HttpFoundation\Request $request */
  72.         $request $event->getRequest();
  73.         /** @var \Symfony\Component\HttpFoundation\JsonResponse $response */
  74.         $response $event->getResponse();
  75.         /* return on invalid requests */
  76.         if (!in_array($event->getResponse()->getStatusCode(), [200204])) {
  77.             return;
  78.         }
  79.         /* $event->getContext() does not exists */
  80.         $context $request->attributes->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
  81.         /* update cookie request on profiles change */
  82.         if (
  83.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.google-authenticator' || 
  84.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.yubico-configurator' ||
  85.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.email-validate' ||
  86.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.disable-validator'
  87.         ) {
  88.             $responseContent json_decode($response->getContent(), true);
  89.             if (!array_key_exists('status'$responseContent['response']) || !$responseContent['response']['status']) {
  90.                 return;
  91.             }
  92.             if ($responseContent['response']['userId']) {
  93.                 $userDB $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->userRepository->search(new Criteria([$responseContent['response']['userId']]), $systemContext)->first());
  94.                 $customFields $userDB->getCustomFields();
  95.                 $twoFaParams $customFields['iwvTwoFactor'] ?? [];
  96.                 $response = !empty($twoFaParams) ? $this->cookieHelperValidator->addTwoFaCookie($response$userDB) : $this->cookieHelperValidator->removeTwoFaCookie($response);
  97.                 $event->setResponse($response);
  98.             }
  99.             return;
  100.         }
  101.         if ($request->attributes->get('_route') !== 'api.oauth.token') {
  102.             return;
  103.         }
  104.     
  105.         $username $request->request->get('username');
  106.         /** @var UserEntity $userDB */
  107.         $userDB $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->userRepository->search((new Criteria())->addFilter(new EqualsFilter('username'$username)), $systemContext)->first());
  108.         
  109.         if(!$userDB){
  110.             return;
  111.         }
  112.         if ($this->cookieHelperValidator->hasScope($request\Shopware\Core\Framework\Api\OAuth\Scope\UserVerifiedScope::IDENTIFIER) ) {
  113.             return;    
  114.         }
  115.         $customFields $userDB->getCustomFields();
  116.         if (!$customFields || !isset($customFields['iwvTwoFactor']) || empty($customFields['iwvTwoFactor']) || ($this->cookieHelperValidator->hasScope($request\Shopware\Core\Framework\Api\OAuth\Scope\UserVerifiedScope::IDENTIFIER) && $this->cookieHelperValidator->hasTwoFaCookie($request$userDB))) {
  117.             return;
  118.         }
  119.         else if($customFields && isset($customFields['iwvTwoFactor'])
  120.         ){
  121.             $isSessionTokenValid $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->sessionTokenHelper->isSessionTokenValid($context$request$userDB));
  122.             if($isSessionTokenValid){
  123.                 $response $this->cookieHelperValidator->addTwoFaCookie($response$userDB);
  124.                 $event->setResponse($response);
  125.                 return;
  126.             }
  127.         }
  128.         $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->emailAuthenticatorAdaptor->load($systemContext$userDB));
  129.         try {
  130.             
  131.             $authenticatorCode = (string) $request->request->get('iwvAuthenticator');
  132.             $authenticatorData = (array) $request->request->get('iwvAuthenticationToken');
  133.             $rememberBrowser = (bool) $request->request->get('iwvTwoFaRememberBrowser');
  134.             if (!isset($authenticatorCode)) {  
  135.                 throw new Exception('OTP is required'1200);
  136.             } elseif (!isset($customFields['iwvTwoFactor'][$authenticatorCode])) {
  137.                 throw new Exception('Authenticator is not configured'1200);
  138.             }
  139.             switch ($authenticatorCode) {
  140.                 case 'emailAuth';
  141.                     if($authenticatorData['sendCodeButton']){
  142.                         $this->emailAuthenticatorAdaptor->sendLoginEmailCode();  
  143.                         throw new Exception('Email has been sent'1203);
  144.                     }
  145.                     else{
  146.                         $emailData $this->emailAuthenticatorAdaptor->authenticate(($authenticatorData['otpValue'] ?? ''));
  147.                         
  148.                         if(!$emailData['verified']){
  149.                             if($emailData['error'] === 'expired'){
  150.                                 throw new Exception('Invalid authentication code'1201);
  151.                             }
  152.                             else{
  153.                                 throw new Exception('Authentication code expired'1201);
  154.                             }
  155.                         }
  156.                         $customFields $this->emailAuthenticatorAdaptor->removeEmailAuthCode();
  157.                     }
  158.                     break;
  159.                 case 'yubico';
  160.                     if (!$this->yubicoAuthenticatorAdaptor->authenticate($customFields['iwvTwoFactor']['yubico']['clientId'], $customFields['iwvTwoFactor']['yubico']['secret'], ($authenticatorData['otpValue'] ?? ''))) {
  161.                         throw new Exception('Invalid authentication code'1201);
  162.                     }
  163.                     break;
  164.                 case 'otp2fa';
  165.                     if (!$this->googleAuthenticatorAdaptor->authenticate($customFields['iwvTwoFactor']['otp2fa']['secret'], ($authenticatorData['otpValue'] ?? ''))) {
  166.                         throw new Exception('Invalid authentication code'1201);
  167.                     }
  168.                     break;
  169.             }
  170.             $response $this->cookieHelperValidator->addTwoFaCookie($response$userDB);
  171.             if($rememberBrowser){
  172.                 $response $this->sessionTokenHelper->createNewSessionCookie($context$response$userDB$customFields);
  173.             }
  174.             else{
  175.                 $response $this->sessionTokenHelper->removeTwoFaCookie($context$response$userDB$customFields);
  176.             }
  177.             
  178.             $event->setResponse($response);
  179.         } catch (\Exception $ex) {
  180.             throw new OAuthServerException('OTP is required'$ex->getCode(), 'iwv-request-otp'401array_keys($customFields['iwvTwoFactor']));
  181.         }
  182.     }
  183. }