Covivo/mobicoop

View on GitHub
api/src/User/Service/SsoManager.php

Summary

Maintainability
A
0 mins
Test Coverage
<?php

/**
 * Copyright (c) 2020, MOBICOOP. All rights reserved.
 * This project is dual licensed under AGPL and proprietary licence.
 ***************************
 *    This program is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Affero General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    This program is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with this program.  If not, see <gnu.org/licenses>.
 ***************************
 *    Licence MOBICOOP described in the file
 *    LICENSE
 */

namespace App\User\Service;

use App\DataProvider\Entity\MobConnect\OpenIdSsoProvider as MobConnectOpenIdSsoProvider;
use App\DataProvider\Entity\OpenIdSsoProvider;
use App\User\Entity\SsoUser;
use App\User\Entity\User;
use App\User\Ressource\SsoConnection;
use Psr\Log\LoggerInterface;

/**
 * SSO manager service.
 *
 * @author Maxime Bardot <maxime.bardot@mobicoop.org>
 */
class SsoManager
{
    public const DEFAULT_RESPONSE_MODE = 'query';
    public const DEFAULT_RESPONSE_TYPE = 'code';
    private const SUPPORTED_PROVIDERS = [
        OpenIdSsoProvider::SSO_PROVIDER_GLCONNECT => OpenIdSsoProvider::class,
        OpenIdSsoProvider::SSO_PROVIDER_PASSMOBILITE => OpenIdSsoProvider::class,
        OpenIdSsoProvider::SSO_PROVIDER_MOBCONNECT => MobConnectOpenIdSsoProvider::class,
        OpenIdSsoProvider::SSO_PROVIDER_MOBCONNECTAUTH => MobConnectOpenIdSsoProvider::class,
        OpenIdSsoProvider::SSO_PROVIDER_MOBCONNECTBASIC => MobConnectOpenIdSsoProvider::class,
        OpenIdSsoProvider::SSO_PROVIDER_MOBIGO => OpenIdSsoProvider::class,
    ];

    private $userManager;
    private $ssoServices;
    private $ssoServicesActive;
    private $ssoUseButtonIcon;
    private $logger;

    public function __construct(UserManager $userManager, array $ssoServices, bool $ssoServicesActive, bool $ssoUseButtonIcon, LoggerInterface $logger)
    {
        $this->userManager = $userManager;
        $this->ssoServices = $ssoServices;
        $this->ssoServicesActive = $ssoServicesActive;
        $this->ssoUseButtonIcon = $ssoUseButtonIcon;
        $this->logger = $logger;
    }

    /**
     * Get all Sso connection services active on this instance.
     *
     * @param string      $baseSiteUri Url of the calling website
     * @param null|string $serviceId   Id of the SSO Service to filter on a specific one
     *
     * @return SsoConnection[]
     */
    public function getSsoConnectionServices(string $baseSiteUri, ?string $serviceId, ?string $redirectUri = null): array
    {
        $ssoServices = [];
        if ($this->ssoServicesActive) {
            foreach ($this->ssoServices as $serviceName => $ssoService) {
                $provider = null;
                if (is_null($serviceId) || $serviceId == $serviceName) {
                    $provider = $this->getSsoProvider($serviceName, $baseSiteUri, $redirectUri);
                }

                if (!is_null($provider)) {
                    $ssoConnection = new SsoConnection($serviceName);
                    $ssoConnection->setUri($provider->getConnectFormUrl());
                    $ssoConnection->setClientId($ssoService['clientId']);
                    $ssoConnection->setService($ssoService['name']);
                    $ssoConnection->setSsoProvider($serviceName);
                    $ssoConnection->setUseButtonIcon($this->ssoUseButtonIcon);
                    $ssoConnection->setExternalAccountDeletion($ssoService['externalAccountDeletion']);
                    $ssoServices[] = $ssoConnection;
                }
            }
        }

        return $ssoServices;
    }

    public function getSsoUserProfile(string $serviceName, string $code, string $baseSiteUri, ?string $redirectUri = null): SsoUser
    {
        $provider = $this->getSsoProvider($serviceName, $baseSiteUri, $redirectUri);
        $provider->setCode($code);

        return $provider->getUserProfile($code);
    }

    /**
     * Get a User from an SSO connection (existing or new one).
     *
     * @param string $serviceName Service name (key in sso.json)
     * @param string $code        Authentification code from SSO service
     * @param string $baseSiteUri Url of the calling website
     */
    public function getUser(string $serviceName, string $code, string $baseSiteUri): User
    {
        $ssoUser = $this->getSsoUserProfile($serviceName, $code, $baseSiteUri);

        return $this->userManager->getUserFromSso($ssoUser);
    }

    /**
     * Get the logout routes of the Sso Services.
     */
    public function logoutSso(?string $idToken = null): array
    {
        if ($this->ssoServicesActive) {
            $logoutUrls = [];
            foreach ($this->ssoServices as $serviceName => $ssoService) {
                $provider = $this->getSsoProvider($serviceName);
                if (!is_null($provider)) {
                    $logOutUrl = $provider->getLogoutUrl($idToken);
                    if (!is_null($logOutUrl)) {
                        $logoutUrls[$serviceName] = $logOutUrl;
                    }
                }
            }

            return count($logoutUrls) > 0 ? [$logoutUrls] : [];
        }

        return [];
    }

    /**
     * Get the logout route of a User.
     */
    public function getSsoLogoutUrl(User $user, string $ssoProvider): ?string
    {
        foreach ($user->getSsoAccounts() as $ssoAccount) {
            if ($ssoAccount->getSsoProvider() == $ssoProvider) {
                foreach ($this->logoutSso($ssoAccount->getIdToken()) as $logOutUrls) {
                    foreach ($logOutUrls as $provider => $logOutUrl) {
                        if ($provider == $ssoProvider) {
                            return $logOutUrl;
                        }
                    }
                }
            }
        }

        return null;
    }

    /**
     * Return instanciated SSoProvider if supported.
     *
     * @var string Name of the SSO Service
     *
     * @param string $baseSiteUri Url of the calling website
     */
    private function getSsoProvider(string $serviceName, string $baseSiteUri = '', ?string $redirectUri = null)
    {
        if (isset(self::SUPPORTED_PROVIDERS[$serviceName])) {
            $service = $this->ssoServices[$serviceName];
            $providerClass = self::SUPPORTED_PROVIDERS[$serviceName];
            $provider = new $providerClass(
                $serviceName,
                $baseSiteUri,
                $service['baseUri'],
                $service['clientId'],
                $service['clientSecret'],
                !is_null($redirectUri) && !empty(trim($redirectUri)) ? $redirectUri : (isset($service['returnUrl']) ? $service['returnUrl'] : SsoConnection::RETURN_URL),
                $service['autoCreateAccount'],
                $service['logOutRedirectUri'],
                $service['codeVerifier'],
                isset($service['response_mode']) ? $service['response_mode'] : self::DEFAULT_RESPONSE_MODE,
                isset($service['response_type']) ? $service['response_type'] : self::DEFAULT_RESPONSE_TYPE
            );
            $provider->setLogger($this->logger);

            return $provider;
        }

        return null;
    }
}