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

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 Exception;
  21. class BackendLoginSubscriber implements EventSubscriberInterface
  22. {
  23.     /**
  24.      * @var EntityRepositoryInterface
  25.      */
  26.     private $userRepository;
  27.     /**
  28.      * @var CookieHelperValidator
  29.      */
  30.     private $cookieHelperValidator;
  31.     /**
  32.      * @var GoogleAuthenticatorAdaptor
  33.      */
  34.     private $googleAuthenticatorAdaptor;
  35.     /**
  36.      * @var YubicoAuthenticatorAdaptor
  37.      */
  38.     private $yubicoAuthenticatorAdaptor;
  39.     /**
  40.      * @var EmailAuthenticatorAdaptor
  41.      */
  42.     private $emailAuthenticatorAdaptor;
  43.     public function __construct(
  44.         EntityRepositoryInterface $userRepository,
  45.         CookieHelperValidator $cookieHelperValidator,
  46.         GoogleAuthenticatorAdaptor $googleAuthenticatorAdaptor,
  47.         YubicoAuthenticatorAdaptor $yubicoAuthenticatorAdaptor,
  48.         EmailAuthenticatorAdaptor $emailAuthenticatorAdaptor
  49.     ) {
  50.         $this->userRepository $userRepository;
  51.         $this->cookieHelperValidator $cookieHelperValidator;
  52.         $this->googleAuthenticatorAdaptor $googleAuthenticatorAdaptor;
  53.         $this->yubicoAuthenticatorAdaptor $yubicoAuthenticatorAdaptor;
  54.         $this->emailAuthenticatorAdaptor $emailAuthenticatorAdaptor;
  55.     }
  56.     public static function getSubscribedEvents()
  57.     {
  58.         return [
  59.             KernelEvents::RESPONSE => 'onResponse',
  60.         ];
  61.     }
  62.     public function onResponse(ResponseEvent $event): void
  63.     {
  64.         /** @var \Symfony\Component\HttpFoundation\Request $request */
  65.         $request $event->getRequest();
  66.         /** @var \Symfony\Component\HttpFoundation\JsonResponse $response */
  67.         $response $event->getResponse();
  68.         /* return on invalid requests */
  69.         if (!in_array($event->getResponse()->getStatusCode(), [200204])) {
  70.             return;
  71.         }
  72.         /* $event->getContext() does not exists */
  73.         $context $request->attributes->get(PlatformRequest::ATTRIBUTE_CONTEXT_OBJECT);
  74.         /* update cookie request on profiles change */
  75.         if (
  76.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.google-authenticator' || 
  77.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.yubico-configurator' ||
  78.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.email-validate' ||
  79.             $request->attributes->get('_route') === 'api.action.iwv.two-factor.backend.disable-validator'
  80.         ) {
  81.             $responseContent json_decode($response->getContent(), true);
  82.             if (!array_key_exists('status'$responseContent['response']) || !$responseContent['response']['status']) {
  83.                 return;
  84.             }
  85.             if ($responseContent['response']['userId']) {
  86.                 $userDB $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->userRepository->search(new Criteria([$responseContent['response']['userId']]), $systemContext)->first());
  87.                 $customFields $userDB->getCustomFields();
  88.                 $twoFaParams $customFields['iwvTwoFactor'] ?? [];
  89.                 $response = !empty($twoFaParams) ? $this->cookieHelperValidator->addTwoFaCookie($response$userDB) : $this->cookieHelperValidator->removeTwoFaCookie($response);
  90.                 $event->setResponse($response);
  91.             }
  92.             return;
  93.         }
  94.         if ($request->attributes->get('_route') !== 'api.oauth.token') {
  95.             return;
  96.         }
  97.     
  98.         $username $request->request->get('username');
  99.         /** @var UserEntity $userDB */
  100.         $userDB $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->userRepository->search((new Criteria())->addFilter(new EqualsFilter('username'$username)), $systemContext)->first());
  101.         if (!$userDB || !($customFields $userDB->getCustomFields()) || !isset($customFields['iwvTwoFactor']) || empty($customFields['iwvTwoFactor']) || ($this->cookieHelperValidator->hasScope($request\Shopware\Core\Framework\Api\OAuth\Scope\UserVerifiedScope::IDENTIFIER) && $this->cookieHelperValidator->hasTwoFaCookie($request$userDB))) {
  102.             return;
  103.         }
  104.         $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->emailAuthenticatorAdaptor->load($systemContext$userDB));
  105.         try {
  106.             
  107.             $authenticatorCode = (string) $request->request->get('iwvAuthenticator');
  108.             $authenticatorData = (array) $request->request->get('iwvAuthenticationToken');
  109.             $context->scope(Context::SYSTEM_SCOPE, fn(Context $systemContext) => $this->emailAuthenticatorAdaptor->load($systemContext$userDB));
  110.             
  111.             if (!isset($authenticatorCode)) {  
  112.                 throw new Exception('OTP is required'1200);
  113.             } elseif (!isset($customFields['iwvTwoFactor'][$authenticatorCode])) {
  114.                 throw new Exception('Authenticator is not configured'1200);
  115.             }
  116.             switch ($authenticatorCode) {
  117.                 case 'emailAuth';
  118.                     if($authenticatorData['sendCodeButton']){
  119.                         $this->emailAuthenticatorAdaptor->sendLoginEmailCode();  
  120.                         throw new Exception('Email has been sent'1203);
  121.                     }
  122.                     else{
  123.                         $emailData $this->emailAuthenticatorAdaptor->authenticate(($authenticatorData['otpValue'] ?? ''));
  124.                         
  125.                         if(!$emailData['verified']){
  126.                             if($emailData['error'] === 'expired'){
  127.                                 throw new Exception('Invalid authentication code'1201);
  128.                             }
  129.                             else{
  130.                                 throw new Exception('Authentication code expired'1201);
  131.                             }
  132.                         }
  133.                     }
  134.                     break;
  135.                 case 'yubico';
  136.                     if (!$this->yubicoAuthenticatorAdaptor->authenticate($customFields['iwvTwoFactor']['yubico']['clientId'], $customFields['iwvTwoFactor']['yubico']['secret'], ($authenticatorData['otpValue'] ?? ''))) {
  137.                         throw new Exception('Invalid authentication code'1201);
  138.                     }
  139.                     break;
  140.                 case 'otp2fa';
  141.                     if (!$this->googleAuthenticatorAdaptor->authenticate($customFields['iwvTwoFactor']['otp2fa']['secret'], ($authenticatorData['otpValue'] ?? ''))) {
  142.                         throw new Exception('Invalid authentication code'1201);
  143.                     }
  144.                     break;
  145.             }
  146.             $response $this->cookieHelperValidator->addTwoFaCookie($response$userDB);
  147.             $event->setResponse($response);
  148.         } catch (\Exception $ex) {
  149.             throw new OAuthServerException('OTP is required'$ex->getCode(), 'iwv-request-otp'401array_keys($customFields['iwvTwoFactor']));
  150.         }
  151.     }
  152. }