<?php declare(strict_types=1);
namespace WabsAuth\Controller;
use Exception;
use Shopware\Core\Checkout\Customer\Event\CustomerLoginEvent;
use Shopware\Core\Checkout\Customer\SalesChannel\AbstractLogoutRoute;
use Shopware\Core\Framework\Routing\Annotation\RouteScope;
use Shopware\Core\Framework\Validation\DataBag\RequestDataBag;
use Shopware\Core\Framework\Validation\Exception\ConstraintViolationException;
use Shopware\Core\System\SalesChannel\Context\SalesChannelContextRestorer;
use Shopware\Core\System\SalesChannel\SalesChannelContext;
use Shopware\Core\System\SystemConfig\SystemConfigService;
use Shopware\Storefront\Controller\StorefrontController;
use Shopware\Storefront\Framework\Captcha\Annotation\Captcha;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Contracts\EventDispatcher\EventDispatcherInterface;
use WabsAuth\Helper\AuthHelper;
use WabsAuth\Helper\DebugHelper;
/**
* @RouteScope(scopes={"storefront"})
*/
class OAuthController extends StorefrontController
{
/**
* @var Session
*/
private $session;
/**
* @var EventDispatcherInterface
*/
private $eventDispatcher;
/**
* @var AuthHelper
*/
private $authHelper;
/**
* @var SalesChannelContextRestorer
*/
private $contextRestorer;
/**
* @var DebugHelper
*/
private $logger;
/**
* @var SystemConfigService
*/
private $systemConfig;
/**
* @var AbstractLogoutRoute
*/
private $logoutRoute;
/**
* OAuthController constructor.
* @param Session $session
* @param SalesChannelContextRestorer $contextRestorer
* @param EventDispatcherInterface $eventDispatcher
* @param AuthHelper $authHelper
* @param SystemConfigService $systemConfig
* @param AbstractLogoutRoute $logoutRoute
* @param DebugHelper $logger
*/
public function __construct(
Session $session,
SalesChannelContextRestorer $contextRestorer,
EventDispatcherInterface $eventDispatcher,
AuthHelper $authHelper,
SystemConfigService $systemConfig,
AbstractLogoutRoute $logoutRoute,
DebugHelper $logger
) {
$this->session = $session;
$this->eventDispatcher = $eventDispatcher;
$this->authHelper = $authHelper;
$this->contextRestorer = $contextRestorer;
$this->logger = $logger;
$this->systemConfig = $systemConfig;
$this->logoutRoute = $logoutRoute;
}
/**
* @Route("/account/oauth/callback", name="frontend.account.oauth.callback", options={"seo"="false"}, methods={"GET"})
*/
public function callback(Request $request, SalesChannelContext $context): Response
{
$receivedValidationCode = $request->query->get('code');
$receivedState = $request->query->get('state');
$redirectRoute = $this->session->get('oauth_redirect_to');
$responseType = $this->session->get('oauth_reponse_type');
$currentState = $this->session->get('oauthState');
$this->session->remove('oauthState');
$this->session->remove('oauth_redirect_to');
$this->session->remove('oauth_reponse_type');
$this->logger->debug('OAuthController | Callback Function Begin.');
if (!isset($currentState)) {
$this->logger->debug('OAuthController | No currentState was set.');
if( $redirectRoute ) {
return $this->redirect($redirectRoute . (preg_match("/\?/", $redirectRoute) ? '&' : '?') . http_build_query(['error' => 'No state']) );
} else {
return $this->redirectToRoute('frontend.account.login.page');
}
}
if (!isset($receivedState) || $currentState != $receivedState) {
$this->logger->debug('OAuthController | The current state and the received state do not match.');
if( $redirectRoute ) {
return $this->redirect($redirectRoute . (preg_match("/\?/", $redirectRoute) ? '&' : '?') . http_build_query(['error' => 'State does not match']) );
} else {
$this->addFlash('danger', 'Ihr Authentifizierungsstatus stimmt nicht mit dem gelieferten Status überein!');
return $this->redirectToRoute('frontend.home.page');
}
}
$provider = $this->authHelper->getProvider();
try {
// Make the token request
$this->logger->debug('OAuthController | Try to get Token.');
$accessToken = $provider->getAccessToken('authorization_code', [
'code' => $receivedValidationCode
]);
$this->logger->debug('OAuthController | Token success: ' . json_encode($accessToken));
$this->logger->debug('OAuthController | Try to get owner data.');
$owner = $provider->getResourceOwner($accessToken);
$ownerData = $owner->toArray();
$this->logger->debug('OAuthController | OwnerData: ' . json_encode($ownerData));
// Make user register if not already registered
// Save users API ID to check for existence
$customer = $this->authHelper->getCustomer($ownerData, $accessToken->getToken(), $context);
if (!$customer) {
$this->logger->debug('OAuthController | No customer returned.');
if( $redirectRoute ) {
return $this->redirect($redirectRoute . (preg_match("/\?/", $redirectRoute) ? '&' : '?') . http_build_query(['error' => 'No customer']) );
} else {
$this->addFlash('danger',
'Wir konnten Sie leider nicht einloggen. Bitte versuchen Sie es zu einem späteren Zeitpunkt erneut.');
return $this->redirectToRoute('frontend.home.page');
}
}
$this->session->set('oauth_access_token', $accessToken);
$this->session->set('oauth_data', $ownerData);
$context = $this->contextRestorer->restore($customer->getId(), $context);
$newToken = $context->getToken();
// Customer is now logged in and has the default channel group!
$event = new CustomerLoginEvent($context, $customer, $newToken);
$this->eventDispatcher->dispatch($event);
$this->logger->debug('OAuthController | Callback Function End.');
if ($redirectRoute) {
return $this->redirect($redirectRoute . ( $responseType === 'code' ? (preg_match("/\?/", $redirectRoute) ? '&' : '?') . http_build_query(['code' => $newToken]) : '' ) );
}
if (!($customer->getGroup()->getName() != 'GC')) {
$this->addFlash('success', 'Login erfolgreich!');
}
return new RedirectResponse(
$this->generateUrl('frontend.home.page', ( $responseType === 'code' ? ['code' => $newToken] : [] )),
302,
['Cache-Control' => 'no-cache']);
} catch (Exception $e) {
$this->logger->error('[OAuth] Login: ' . $e->getMessage());
$this->logger->error('[OAuth] Login: ' . $e->getTraceAsString());
$request->getSession()->invalidate();
$this->logger->debug('OAuthController | Callback Function End Exception.');
if( $redirectRoute ) {
return $this->redirect($redirectRoute . (preg_match("/\?/", $redirectRoute) ? '&' : '?') . http_build_query(['error' => $e->getMessage()]) );
} else {
$this->addFlash(
'danger',
'Wir konnten Sie leider nicht einloggen. Bitte versuchen Sie es zu einem späteren Zeitpunkt erneut. Bitte prüfen Sie auch die Vollständigkeit Ihrer Daten Ihres IGM-Accounts.'
);
return $this->redirectToRoute('frontend.home.page');
}
}
}
/**
* @Route("/account/oauth/redirect", name="frontend.account.oauth.redirect", options={"seo"="false"}, methods={"GET"})
*/
public function oAuthRedirect(Request $request, SalesChannelContext $context)
{
$provider = $this->authHelper->getProvider();
// First get URL to generate the state and then save the state to the session
$url = $provider->getAuthorizationUrl();
$this->session->set('oauthState', $provider->getState());
if( $request->query->get('response_type')) {
$this->session->set('oauth_reponse_type', $request->query->get('response_type') );
}
if( $request->query->get('redirect_to')) {
$this->session->set('oauth_redirect_to', $request->query->get('redirect_to') );
}
if( $request->query->get('auto_group') ) {
$this->session->set('oauth_auto_group', $request->query->get('auto_group') );
}
if( $request->query->get('force_auto_group') ) {
$this->session->set('oauth_force_auto_group', (bool)$request->query->get('force_auto_group') );
}
return $this->redirect($url);
}
/**
* @Route("/account/register", name="frontend.account.register.save", methods={"POST"})
* @Captcha
*/
public function register(Request $request, RequestDataBag $data, SalesChannelContext $context): Response
{
// we don't let ppl register normally! so we don't need this route
return $this->redirectToRoute('frontend.home.page');
}
/**
* @Route("/account/profile", name="frontend.account.profile.save", methods={"POST"})
*/
public function saveProfile(RequestDataBag $data, SalesChannelContext $context): Response
{
// buttons already removed. there should be no possible way to change profile information from controller
return $this->redirectToRoute('frontend.home.page');
}
/**
* Route to change
*
* @Route("/account/group/change", name="frontend.account.group.change", methods={"GET"})
*/
public function changeGroup(Request $request, SalesChannelContext $context): Response
{
try {
$this->logger->debug('OAuthController | ChangeGroup Function Begin.');
$redirectRoute = $this->session->get('oauth_redirect_to');
// Get selected group and load customer group (not yet loaded by default)
$selectedGroup = $request->query->get('group-selection');
$customer = $this->authHelper->loadCustomerGroupToCustomer($context->getCustomer(), $context);
// Check if user is allowed to change group and to use the transmitted group
if ($this->authHelper->checkAllowedGroups($selectedGroup, $customer)) {
$customFields = $customer->getCustomFields();
// Check if we have all needed Data in customer->customFields
if (!array_key_exists('igm_session_token', $customFields) ||
!array_key_exists('igm_groups', $customFields) ||
!array_key_exists('access_token', $customFields) ||
!array_key_exists('igm_account_data', $customFields)) {
$this->logger->debug('OAuthController | ChangeGroup | Array keys missing from customFields: ' . $customFields);
throw new Exception('Not enough data saved to the customer.');
}
// Replicate the needed data array for the update process
$data = [
'igm_session_token' => $customFields['igm_session_token'],
'igm_account_data' => $customFields['igm_account_data']
];
$igmGroups = $customFields['igm_groups'];
$accessToken = $customFields['access_token'];
// Check User group
$customerGroupId = $this->authHelper->loadCustomerGroupId($selectedGroup, $context);
if (empty($customerGroupId)) {
$this->logger->debug('AuthHelper | No Valid Group for customer with number: ' . $customer->getCustomerNumber());
throw new Exception('No valid Group for customer.');
}
// User is allowed, so try to set the group
$this->authHelper->updateCustomer($customer, $data, $customerGroupId, $igmGroups, $accessToken,
$context);
if ($this->authHelper->organizationAddressFlag) {
return $this->redirectToRoute('frontend.account.logout.page', ['organizationAddressFlag' => true]);
}
$this->logger->debug('OAuthController | ChangeGroup Function End.');
if ($redirectRoute) {
return $this->redirect($redirectRoute);
}
return $this->redirectToRoute('frontend.home.page');
}
} catch (Exception $e) {
$this->logger->error('[OAuth] Login: ' . $e->getMessage());
$this->logger->error('[OAuth] Login: ' . $e->getTraceAsString());
$request->getSession()->invalidate();
$this->addFlash(
'danger',
'Wir konnten Sie leider nicht einloggen. Bitte versuchen Sie es zu einem späteren Zeitpunkt erneut. Bitte prüfen Sie auch die Vollständigkeit Ihrer Daten Ihres IGM-Accounts.'
);
$this->logger->debug('OAuthController | ChangeGroup Function Exception.');
return $this->redirectToRoute('frontend.home.page');
}
// User tried to login to a group that he has no permission for
// Might be wise to add logging for this kind of user, because they try to hack us...
$this->addFlash('error', 'Sie haben keine Berechtigung für diese Gruppe.');
$redirectRoute = $this->session->get('oauth_redirect_to');
$this->logger->debug('OAuthController | ChangeGroup Function Error.');
return $this->redirectToRoute($redirectRoute ?? 'frontend.home.page');
}
/**
* @Route("/account/logout", name="frontend.account.logout.page", methods={"GET"})
*/
public function logout(Request $request, SalesChannelContext $context): Response
{
$parameters = [];
$redirectTo = $request->query->get('redirect_to') ? $request->query->get('redirect_to') : null;
if ($context->getCustomer() !== null) {
try {
$this->logoutRoute->logout($context, new RequestDataBag());
$salesChannelId = $context->getSalesChannel()->getId();
if ($request->hasSession() && $this->systemConfig->get(
'core.loginRegistration.invalidateSessionOnLogOut',
$salesChannelId)) {
$request->getSession()->invalidate();
}
if (!$redirectTo) {
if ($request->get('organizationAddressFlag')) {
$this->addFlash('danger', $this->systemConfig->get('WabsAuth.config.invalidOrganizationAddressErrorMessage'));
}
$this->addFlash('success', $this->trans('account.logoutSucceeded'));
}
} catch (ConstraintViolationException $formViolations) {
$parameters = ['formViolations' => $formViolations];
}
}
if( $redirectTo ) $redirectTo .= (preg_match("/\?/", $redirectTo) ? '&' : '?') . http_build_query($parameters);
/** Edit: Changed Redirect URL to KeyCloak Service if set*/
$logoutUrl = $this->systemConfig->get('WabsAuth.config.urlLogout');
if (!$logoutUrl) {
if( $redirectTo ) {
return $this->redirect($redirectTo);
} else {
return $this->redirectToRoute('frontend.home.page', $parameters);
}
}
return $this->redirect($logoutUrl . '?redirect_uri=' . ( $redirectTo ? $redirectTo : $this->generateUrl('frontend.home.page',
$parameters, UrlGeneratorInterface::ABSOLUTE_URL)));
/** END EDIT */
}
}