Leuchtfeuer/auth0-for-typo3

View on GitHub
Classes/LoginProvider/Auth0Provider.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

declare(strict_types=1);

/*
 * This file is part of the "Auth0" extension for TYPO3 CMS.
 *
 * For the full copyright and license information, please read the
 * LICENSE.txt file that was distributed with this source code.
 *
 * Florian Wessels <f.wessels@Leuchtfeuer.com>, Leuchtfeuer Digital Marketing
 */

namespace Leuchtfeuer\Auth0\LoginProvider;

use Auth0\SDK\Auth0;
use Auth0\SDK\Exception\ConfigurationException;
use GuzzleHttp\Exception\GuzzleException;
use Leuchtfeuer\Auth0\Domain\Model\Application;
use Leuchtfeuer\Auth0\Domain\Repository\ApplicationRepository;
use Leuchtfeuer\Auth0\Domain\Transfer\EmAuth0Configuration;
use Leuchtfeuer\Auth0\Factory\ApplicationFactory;
use Leuchtfeuer\Auth0\Middleware\CallbackMiddleware;
use Leuchtfeuer\Auth0\Utility\ModeUtility;
use Leuchtfeuer\Auth0\Utility\TokenUtility;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerAwareTrait;
use TYPO3\CMS\Backend\Controller\LoginController;
use TYPO3\CMS\Backend\LoginProvider\LoginProviderInterface;
use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManager;
use TYPO3\CMS\Extbase\Configuration\ConfigurationManagerInterface;
use TYPO3\CMS\Extbase\Configuration\Exception\InvalidConfigurationTypeException;
use TYPO3\CMS\Fluid\View\StandaloneView;

class Auth0Provider implements LoginProviderInterface, LoggerAwareInterface, SingletonInterface
{
    use LoggerAwareTrait;

    public const ACTION_LOGIN = 'login';

    public const ACTION_LOGOUT = 'logout';

    public const LOGIN_PROVIDER = 1526966635;

    protected ?Application $application = null;

    protected Auth0 $auth0;

    protected ?array $userInfo = [];

    protected EmAuth0Configuration $configuration;

    protected ?string $action;

    protected array $frameworkConfiguration;

    /**
     * @throws InvalidConfigurationTypeException
     */
    public function __construct(ConfigurationManager $configurationManager)
    {
        $this->configuration = new EmAuth0Configuration();
        $this->application = GeneralUtility::makeInstance(ApplicationRepository::class)->findByUid($this->configuration->getBackendConnection());
        $this->frameworkConfiguration = $configurationManager->getConfiguration(ConfigurationManagerInterface::CONFIGURATION_TYPE_FRAMEWORK, 'auth0');
    }

    public function render(StandaloneView $view, PageRenderer $pageRenderer, LoginController $loginController): void
    {
        $this->logger->notice('Auth0 login is used.');

        // Figure out whether TypoScript is loaded
        if (!$this->isTypoScriptLoaded()) {
            // In this case we need a default template
            $this->getDefaultView($view, $pageRenderer);
            return;
        }

        $this->prepareView($view, $pageRenderer);

        // Throw error if there is no application
        if (!$this->application) {
            $view->assign('error', 'no_application');
            return;
        }

        // Try to get user info from session storage
        $this->userInfo = $this->getUserInfo();

        $urlData = GeneralUtility::_GET('auth0') ?? [];
        $this->action = $urlData['action'] ?? null;

        if ((empty($this->userInfo) && $this->action === self::ACTION_LOGIN) || $this->action === self::ACTION_LOGOUT) {
            $this->handleRequest();
        }

        // Assign variables and Auth0 response to view
        $view->assignMultiple([
            'auth0Error' => GeneralUtility::_GET('error'),
            'auth0ErrorDescription' => GeneralUtility::_GET('error_description'),
            'code' => GeneralUtility::_GET('code'),
            'userInfo' => $this->userInfo,
        ]);
    }

    protected function setAuth0(): bool
    {
        try {
            $this->auth0 = ApplicationFactory::build($this->configuration->getBackendConnection());
        } catch (\Exception $exception) {
            $this->logger->critical($exception->getMessage());
            return false;
        } catch (GuzzleException $exception) {
            $this->logger->critical($exception->getMessage());
            return false;
        }

        return true;
    }

    protected function getCallback(?string $redirectUri = ''): string
    {
        $tokenUtility = new TokenUtility();
        $tokenUtility->withPayload('environment', ModeUtility::BACKEND_MODE);
        $tokenUtility->withPayload('application', $this->configuration->getBackendConnection());

        if ($redirectUri !== '') {
            $tokenUtility->withPayload('redirectUri', $redirectUri);
        }

        return sprintf(
            '%s%s?%s=%s',
            $tokenUtility->getIssuer(),
            CallbackMiddleware::PATH,
            CallbackMiddleware::TOKEN_PARAMETER,
            $tokenUtility->buildToken()->toString()
        );
    }

    protected function getUserInfo()
    {
        $this->setAuth0();
        $userInfo = $this->auth0->configuration()->getSessionStorage()->get('user');
        if (empty($userInfo)) {
            try {
                $this->logger->notice('Try to get user via Auth0 API');
                if ($this->auth0->exchange($this->getCallback(), GeneralUtility::_GET('code'), GeneralUtility::_GET('state'))) {
                    $userInfo = $this->auth0->getUser();
                }
            } catch (\Exception $exception) {
                $this->logger->critical($exception->getMessage());
                $this->auth0->clear();
            }
        }

        return $userInfo;
    }

    /**
     * @throws ConfigurationException
     */
    protected function handleRequest(): void
    {
        if ($this->action === self::ACTION_LOGOUT) {
            // Logout user from Auth0
            $this->logger->notice('Logout user.');
            $this->logoutFromAuth0();
        } elseif ($this->action === self::ACTION_LOGIN) {
            // Login user to Auth0
            $this->logger->notice('Handle backend login.');
            header('Location: ' . $this->auth0->login($this->getCallback()));
        }
    }

    protected function isTypoScriptLoaded(): bool
    {
        return isset($this->frameworkConfiguration['settings']['stylesheet']);
    }

    protected function prepareView(StandaloneView &$standaloneView, PageRenderer &$pageRenderer): void
    {
        $standaloneView->setTemplate($this->getTemplateName());
        $standaloneView->setLayoutRootPaths($this->frameworkConfiguration['view']['layoutRootPaths']);
        $standaloneView->setTemplateRootPaths($this->frameworkConfiguration['view']['templateRootPaths']);

        $pageRenderer->addCssFile($this->frameworkConfiguration['settings']['stylesheet']);
    }

    protected function getDefaultView(StandaloneView &$standaloneView, PageRenderer &$pageRenderer): void
    {
        $standaloneView->setLayoutRootPaths(['EXT:auth0/Resources/Private/Layouts/']);
        $standaloneView->setTemplatePathAndFilename(
            GeneralUtility::getFileAbsFileName('EXT:auth0/Resources/Private/Templates/' . $this->getTemplateName() . '.html')
        );
        $standaloneView->assign('error', 'no_typoscript');
        $pageRenderer->addCssFile('EXT:auth0/Resources/Public/Styles/backend.css');
    }

    /**
     * @throws ConfigurationException
     */
    protected function logoutFromAuth0(): void
    {
        $redirectUri = GeneralUtility::getIndpEnv('TYPO3_SITE_URL') . 'typo3/logout';
        if ($this->application->isSingleLogOut() && $this->configuration->isSoftLogout()) {
            $this->auth0->clear();
            header('Location: ' . $redirectUri);
        } else {
            header('Location: ' . $this->auth0->logout($this->getCallback($redirectUri)));
        }
        exit();
    }

    private function getTemplateName(): string
    {
        $templateName = ModeUtility::isTYPO3V12() ? 'BackendV12' : 'BackendV11';

        return 'LoginProvider/' . $templateName;
    }
}