<?php declare(strict_types=1);
namespace Moorl\SignIn\Core\Subscriber;
use League\OAuth2\Client\Provider\Exception\IdentityProviderException;
use Moorl\SignIn\Core\Service\SignInService;
use Moorl\SignIn\MoorlSignIn;
use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
use Shopware\Core\Checkout\Customer\Event\CustomerLogoutEvent;
use Shopware\Core\Framework\Adapter\Cache\CacheStateSubscriber;
use Shopware\Core\Framework\Context;
use Shopware\Core\Framework\Log\LoggerFactory;
use Shopware\Core\Framework\Uuid\Uuid;
use Shopware\Core\SalesChannelRequest;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Framework\Cache\CacheResponseSubscriber;
use Shopware\Storefront\Framework\Cache\Event\HttpCacheGenerateKeyEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\Event\ExceptionEvent;
use Symfony\Component\HttpKernel\Event\RequestEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouterInterface;
use Psr\Log\LoggerInterface;
use Monolog\Logger;
class KernelEventSubscriber implements EventSubscriberInterface
{
private SystemConfigService $systemConfigService;
private RequestStack $requestStack;
private RouterInterface $router;
private SignInService $signInService;
private LoggerInterface $logger;
public function __construct(
RequestStack $requestStack,
RouterInterface $router,
SystemConfigService $systemConfigService,
SignInService $signInService,
LoggerFactory $loggerFactory
) {
$this->requestStack = $requestStack;
$this->router = $router;
$this->systemConfigService = $systemConfigService;
$this->signInService = $signInService;
$this->logger = $loggerFactory->createRotating(
'moorl_sign_in',
7,
(int) $systemConfigService->get('MoorlSignIn.config.logLevel') ?: Logger::DEBUG
);
}
public static function getSubscribedEvents(): array
{
return [
KernelEvents::REQUEST => 'checkToken',
KernelEvents::EXCEPTION => 'tokenNotSetHandler',
HttpCacheGenerateKeyEvent::class => 'onHttpCacheGenerateKeyEvent',
CustomerLoginEvent::class => 'onCustomerLogin',
CustomerLogoutEvent::class => 'onCustomerLogout'
];
}
public function onHttpCacheGenerateKeyEvent(HttpCacheGenerateKeyEvent $event): void
{
$request = $event->getRequest();
$systemState = $request->cookies->get(CacheResponseSubscriber::SYSTEM_STATE_COOKIE);
if (!$systemState) {
return;
}
$isLoggedIn = mb_strpos($systemState, CacheStateSubscriber::STATE_LOGGED_IN) !== false;
if (!$isLoggedIn) {
return;
}
$hash = $event->getHash();
$hash = hash('sha256', $hash . '-' . serialize(CacheStateSubscriber::STATE_LOGGED_IN));
$event->setHash($hash);
}
public function onCustomerLogin(CustomerLoginEvent $event): void
{
$master = $this->requestStack->getMasterRequest();
$session = $master->getSession();
$session->set(MoorlSignIn::SKIP_CHECK_TOKEN, true);
}
public function onCustomerLogout(CustomerLogoutEvent $event): void
{
$master = $this->requestStack->getMasterRequest();
$session = $master->getSession();
$session->remove(MoorlSignIn::SKIP_CHECK_TOKEN);
}
public function tokenNotSetHandler(ExceptionEvent $event): void
{
if (!$event->getThrowable() instanceof IdentityProviderException) {
return;
}
$redirectUrl = $this->systemConfigService->get('MoorlSignIn.config.redirectUrl');
if (!$redirectUrl) {
return;
}
$redirectResponse = new RedirectResponse($redirectUrl);
$event->setResponse($redirectResponse);
}
public function checkToken(RequestEvent $event): void
{
// Is is not sales channel request then do nothing
if (!$event->getRequest()->attributes->has(SalesChannelRequest::ATTRIBUTE_IS_SALES_CHANNEL_REQUEST)) {
return;
}
$salesChannelId = $event->getRequest()->attributes->get('sw-sales-channel-id');
if (!$salesChannelId) {
return;
}
$master = $this->requestStack->getMasterRequest();
$routeBlacklist = $this->systemConfigService->get('MoorlSignIn.config.routeBlacklist', $salesChannelId);
if ($routeBlacklist) {
if (!in_array($master->attributes->get('_route'), $routeBlacklist)) {
return;
}
}
// If no redirect set, do nothing
$redirectTo = $this->systemConfigService->get('MoorlSignIn.config.redirectTo', $salesChannelId);
if (!$redirectTo) {
return;
}
if ($redirectTo === 'redirectUrl') {
// Fixed redirect
$redirectResponse = new RedirectResponse(
$this->systemConfigService->get('MoorlSignIn.config.redirectUrl', $salesChannelId)
);
} else {
// Router redirect
$redirectResponse = new RedirectResponse(
$this->router->generate($redirectTo, $master->query->all())
);
}
$redirectActive = $this->systemConfigService->get('MoorlSignIn.config.redirectActive', $salesChannelId);
// No Session set, Redirect is Active
if ((!$master || !$master->hasSession()) && $redirectActive) {
$this->logger->info('No Session set, Redirect is Active');
$event->setResponse($redirectResponse);
return;
}
$session = $master->getSession();
$lastPage = $master->server->get('REQUEST_SCHEME') . '://' . $master->server->get('HTTP_HOST') . $master->server->get('REDIRECT_URL');
// Fetch query data
$queryData = $master->query->all();
// Get name of query param for the token
$queryParam = $this->systemConfigService->get('MoorlSignIn.config.tokenQueryParam', $salesChannelId);
// Token is not set by default
$token = null;
if ($queryParam) {
$token = $master->query->get($queryParam);
// Remove token query param and rebuild other query data
unset($queryData[$queryParam]);
}
if ($queryData) {
// Last page with GET params but no token
$lastPageAll = $lastPage . '?' . http_build_query($queryData);
} else {
// Last page
$lastPageAll = $lastPage;
}
// Remember last page
$session->set(MoorlSignIn::LAST_KNOWN_PAGE, $lastPageAll);
$isLoggedIn = $session->get(MoorlSignIn::SKIP_CHECK_TOKEN);
// User not Logged In, No Token Set, Redirect is Active
if (!$isLoggedIn && !$token && $redirectActive) {
$this->logger->info('User not Logged In, No Token Set, Redirect is Active');
$event->setResponse($redirectResponse);
return;
}
// Token Set
if ($token) {
$this->logger->info('Token Set', $master->query->all());
if ($master->query->get('PayerID') || $master->query->get('_sw_payment_token')) {
$this->logger->info('Payment token detected');
return; // Skip PayPal token
}
$state = Uuid::randomHex();
$providerName = $this->systemConfigService->get('MoorlSignIn.config.tokenProvider', $salesChannelId);
$data = [
'provider' => $providerName,
'lastPage' => $lastPage,
'lastPageAll' => $lastPageAll,
'token' => $token,
'state' => $state
];
$this->logger->info('Create new login session', $data);
$this->signInService->createSession($data, Context::createDefaultContext());
$redirectResponse = new RedirectResponse($this->router->generate('moorl.oauth2.login', ['state' => $state]));
$event->setResponse($redirectResponse);
sleep(1);
return;
}
$this->logger->info('No Token Set', $master->query->all());
// User not Logged In, Redirect is Active
if (!$isLoggedIn && $redirectActive) {
$this->logger->info('User not Logged In, Redirect is Active');
$event->setResponse($redirectResponse);
}
}
}