/home/ivoiecob/email.hirewise-va.com/modules/OAuthIntegratorWebclient/Module.php
<?php
/**
 * This code is licensed under AGPLv3 license or Afterlogic Software License
 * if commercial version of the product was purchased.
 * For full statements of the licenses see LICENSE-AFTERLOGIC and LICENSE-AGPL3 files.
 */

namespace Aurora\Modules\OAuthIntegratorWebclient;

use Aurora\Api;

/**
 * Brings oAuth support into Aurora platform.
 *
 * @license https://www.gnu.org/licenses/agpl-3.0.html AGPL-3.0
 * @license https://afterlogic.com/products/common-licensing Afterlogic Software License
 * @copyright Copyright (c) 2023, Afterlogic Corp.
 *
 * @internal
 * @property Settings $oModuleSettings
 *
 * @package Modules
 */
class Module extends \Aurora\System\Module\AbstractWebclientModule
{
    public $oManager = null;

    /**
     * @return Module
     */
    public static function getInstance()
    {
        return parent::getInstance();
    }

    /**
     * @return Module
     */
    public static function Decorator()
    {
        return parent::Decorator();
    }

    /**
     * @return Settings
     */
    public function getModuleSettings()
    {
        return $this->oModuleSettings;
    }

    /***** private functions *****/
    /**
     * Initializes module.
     *
     * @ignore
     */
    public function init()
    {
        include_once __DIR__ . '/Classes/OAuthClient/http.php';
        include_once __DIR__ . '/Classes/OAuthClient/oauth_client.php';

        $this->aErrors = [
            Enums\ErrorCodes::ServiceNotAllowed			=> $this->i18N('ERROR_SERVICE_NOT_ALLOWED'),
            Enums\ErrorCodes::AccountNotAllowedToLogIn	=> $this->i18N('ERROR_ACCOUNT_NOT_ALLOWED'),
            Enums\ErrorCodes::AccountAlreadyConnected	=> $this->i18N('ERROR_ACCOUNT_ALREADY_CONNECTED'),
        ];

        $this->oManager = new Manager($this);

        $this->AddEntry('oauth', 'OAuthIntegratorEntry');
        $this->includeTemplate('StandardLoginFormWebclient_LoginView', 'Login-After', 'templates/SignInButtonsView.html', self::GetName());
        $this->includeTemplate('StandardRegisterFormWebclient_RegisterView', 'Register-After', 'templates/SignInButtonsView.html', self::GetName());
        $this->subscribeEvent('Core::DeleteUser::after', array($this, 'onAfterDeleteUser'));
        $this->subscribeEvent('Core::GetAccounts', array($this, 'onGetAccounts'));

        $this->denyMethodsCallByWebApi([
            'GetAccessToken',
            'GetAccount'
        ]);
    }

    /**
     * Deletes all oauth accounts which are owened by the specified user.
     *
     * @ignore
     * @param array $aArgs
     * @param mixed $mResult
     */
    public function onAfterDeleteUser($aArgs, &$mResult)
    {
        if ($mResult) {
            $this->oManager->deleteAccountByUserId($aArgs['UserId']);
        }
    }

    /**
     *
     * @param array $aArgs
     * @param array $aResult
     */
    public function onGetAccounts($aArgs, &$aResult)
    {
        if (isset($aArgs['UserId'])) {
            $mAccounts = $this->oManager->getAccounts($aArgs['UserId']);

            foreach ($mAccounts as $oAccount) {
                $aResult[] = array(
                    'Type' => $oAccount->getName(),
                    'Module' => $this->GetName(),
                    'Id' => $oAccount->Id,
                    'Email' => $oAccount->Email
                );
            }
        }
    }
    /***** private functions *****/

    /***** public functions *****/
    /**
     * @ignore
     */
    public function OAuthIntegratorEntry()
    {
        $mResult = false;
        $sOAuthArg = $this->oHttp->GetQuery('oauth', '');
        $sOAuthArg = \explode('-', $sOAuthArg);

        if (isset($sOAuthArg[1]) && $sOAuthArg[1] === 'connect') {
            $aArgs['Service'] = $sOAuthArg[0];
            $this->broadcastEvent(
                'ResetAccessToken',
                $aArgs,
                $mResult
            );
            $mResult = false;
            Api::Location2('./?oauth=' . $sOAuthArg[0]);
        }

        $sService = $this->oHttp->GetQuery('oauth', '');
        $aArgs = array(
            'Service' => $sService
        );

        if (!isset($_SESSION['AuroraUserId'])) {
            $_SESSION['AuroraUserId'] = Api::getAuthenticatedUserId();
        }
        $this->broadcastEvent(
            'OAuthIntegratorAction',
            $aArgs,
            $mResult
        );

        $sOAuthIntegratorRedirect = isset($_COOKIE["oauth-redirect"]) ? $_COOKIE["oauth-redirect"] : 'login';

        $sError = $this->oHttp->GetQuery('error', null);
        if (isset($sError)) {
            $sInvitationLinkHash =  isset($_COOKIE["InvitationLinkHash"]) ? $_COOKIE["InvitationLinkHash"] : null;
            if ($sOAuthIntegratorRedirect === 'register' && isset($sInvitationLinkHash)) {
                Api::Location2(
                    './#register/' . $sInvitationLinkHash
                );
            }
        }
        if (false !== $mResult && \is_array($mResult) && !isset($mResult['error'])) {
            $iAuthUserId = Api::getAuthenticatedUserId();
            if (!$iAuthUserId && isset($_SESSION['AuroraUserId'])) {
                $iAuthUserId = $_SESSION['AuroraUserId'];
                unset($_SESSION['AuroraUserId']);
            }

            $oUser = null;

            $oOAuthAccount = $this->oManager->getAccountById($mResult['id'], $mResult['type']);
            if ($oOAuthAccount) {
                if ($sOAuthIntegratorRedirect === 'register') {
                    Api::Location2(
                        './?error=' . Enums\ErrorCodes::AccountAlreadyConnected . '&module=' . self::GetName()
                    );
                }

                if (!$oOAuthAccount->issetScope('auth') && $sOAuthIntegratorRedirect !== 'connect') {
                    Api::Location2(
                        './?error=' . Enums\ErrorCodes::AccountNotAllowedToLogIn . '&module=' . self::GetName()
                    );
                }

                $oOAuthAccount->AccessToken = isset($mResult['access_token']) ? $mResult['access_token'] : '';
                $oOAuthAccount->RefreshToken = isset($mResult['refresh_token']) ? $mResult['refresh_token'] : '';
                $oOAuthAccount->Name = $mResult['name'];
                $oOAuthAccount->Email = $mResult['email'];
                if ($sOAuthIntegratorRedirect !== 'login') {
                    $oOAuthAccount->Scopes = '';
                    $oOAuthAccount->setScopes(
                        $mResult['scopes']
                    );
                }
                $this->oManager->updateAccount($oOAuthAccount);

                $oUser = Api::getUserById($oOAuthAccount->IdUser);
            } else {
                if ($iAuthUserId) {
                    $aArgs = array(
                        'UserName' => $mResult['name'],
                        'UserId' => $iAuthUserId
                    );
                    $this->broadcastEvent(
                        'CreateAccount',
                        $aArgs,
                        $oUser
                    );
                }

                $aArgs = array();
                $this->broadcastEvent(
                    'CreateOAuthAccount',
                    $aArgs,
                    $oUser
                );

                if (!($oUser instanceof \Aurora\Modules\Core\Models\User) && ($sOAuthIntegratorRedirect === 'register' || $this->oModuleSettings->AllowNewUsersRegister)) {
                    $bPrevState = Api::skipCheckUserRole(true);

                    try {
                        $iUserId = \Aurora\Modules\Core\Module::Decorator()->CreateUser(0, $mResult['email']);
                        if ($iUserId) {
                            $oUser = Api::getUserById($iUserId);
                        }
                    } catch (\Aurora\System\Exceptions\ApiException $oException) {
                        if ($oException->getCode() === \Aurora\System\Notifications::UserAlreadyExists) {
                            \Aurora\System\Api::Location2(
                                './?error=' . Enums\ErrorCodes::AccountAlreadyConnected . '&module=' . self::GetName()
                            );
                        }
                    }

                    Api::skipCheckUserRole($bPrevState);
                }

                if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
                    $oOAuthAccount = new Models\OauthAccount();
                    $oOAuthAccount->IdSocial = $mResult['id'];
                    $oOAuthAccount->IdUser = $oUser->Id;
                    $oOAuthAccount->Type = $mResult['type'];
                    $oOAuthAccount->AccessToken = isset($mResult['access_token']) ? $mResult['access_token'] : '';
                    $oOAuthAccount->RefreshToken = isset($mResult['refresh_token']) ? $mResult['refresh_token'] : '';
                    $oOAuthAccount->Name = $mResult['name'];
                    $oOAuthAccount->Email = $mResult['email'];
                    $oOAuthAccount->setScopes(
                        $mResult['scopes']
                    );
                    $this->oManager->createAccount($oOAuthAccount);
                }
            }

            if ($sOAuthIntegratorRedirect === 'login' || $sOAuthIntegratorRedirect === 'register') {
                if ($oUser) {
                    $sAuthToken = Api::UserSession()->Set(
                        \Aurora\System\UserSession::getTokenData($oOAuthAccount, true),
                        \time() + 60 * 60 * 24 * 30
                    );

                    Api::setAuthTokenCookie($sAuthToken);

                    //this will store user data in static variable of Api class for later usage
                    Api::getAuthenticatedUser($sAuthToken);

                    if ($this->oHttp->GetQuery('mobile', '0') === '1') {
                        return json_encode(
                            array(
                                \Aurora\System\Application::AUTH_TOKEN_KEY => $sAuthToken
                            )
                        );
                    } else {
                        Api::Location2('./');
                    }
                } else {
                    Api::Location2(
                        './?error=' . Enums\ErrorCodes::AccountNotAllowedToLogIn . '&module=' . self::GetName()
                    );
                }
            } else {
                $sResult = $mResult !== false ? \json_encode($mResult) : 'false';
                $sErrorCode = '';

                if ($oUser && $iAuthUserId && $oUser->Id !== $iAuthUserId) {
                    $sResult = 'false';
                    $sErrorCode = Enums\ErrorCodes::AccountAlreadyConnected;
                }

                self::EchoJsCallback($mResult['type'], $sResult, $sErrorCode);
            }
        } else {
            self::EchoJsCallback($sService, 'false', '');
        }
    }

    protected static function EchoJsCallback($sType, $sResult, $sErrorCode)
    {
        if (in_array($sType, self::Decorator()->GetServiceTypes())) {
            echo
            "<script>"
                . "try {"
                . "  if (typeof(window.opener." . $sType . "ConnectCallback) !== 'undefined') {"
                . "    window.opener." . $sType . "ConnectCallback(" . $sResult . ", '" . $sErrorCode . "','" . self::GetName() . "');"
                . "  }"
                . "} finally  {"
                . "  window.close();"
                . "}"
            . "</script>";
        } else {
            http_response_code(404);
        }
        exit;
    }

    /**
     * Returns oauth account with specified type.
     *
     * @param string $Type Type of oauth account.
     * @param string $Email
     * @param int $UserId Id of user.
     * @return \Aurora\Modules\OAuthIntegratorWebclient\Models\OauthAccount
     */
    public function GetAccount($Type, $Email = '', $UserId = null)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        return $this->oManager->getAccount(
            $UserId ? $UserId : \Aurora\System\Api::getAuthenticatedUserId(),
            $Type,
            $Email
        );
    }

    /**
     * Updates oauth acount.
     *
     * @param \Aurora\Modules\OAuthIntegratorWebclient\Models\OauthAccount $oAccount Oauth account.
     * @return boolean
     */
    public function UpdateAccount(\Aurora\Modules\OAuthIntegratorWebclient\Models\OauthAccount $oAccount)
    {
        return $this->oManager->updateAccount($oAccount);
    }
    /***** public functions *****/

    /***** public functions might be called with web API *****/
    /**
     * Returns all oauth services names.
     *
     * @return array
     */
    public function GetServices()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return [];
    }

    /***** public functions might be called with web API *****/
    /**
     * Returns all oauth services types.
     *
     * @return array
     */
    public function GetServiceTypes()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        return [];
    }

    /**
     * Returns all oauth services settings for authenticated user.
     *
     * @return array
     */
    public function GetSettings()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        $aSettings = array();

        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser && $oUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
            $aArgs = array();
            $aServices = array();
            $this->broadcastEvent(
                'GetServicesSettings',
                $aArgs,
                $aServices
            );
            $aSettings['Services'] = $aServices;
        }

        if ($oUser && $oUser->isNormalOrTenant()) {
            $aSettings['AuthModuleName'] = $this->oModuleSettings->AuthModuleName;
            $aSettings['OnlyPasswordForAccountCreate'] = $this->oModuleSettings->OnlyPasswordForAccountCreate;
        }

        return $aSettings;
    }

    /**
     * Updates all oauth services settings.
     *
     * @param array $Services Array with services settings passed by reference.
     *
     * @return boolean
     */
    public function UpdateSettings($Services)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::TenantAdmin);

        $aArgs = array(
            'Services' => $Services
        );
        $this->broadcastEvent(
            'UpdateServicesSettings',
            $aArgs
        );

        return true;
    }

    /**
     * Get all oauth accounts.
     *
     * @return array
     */
    public function GetAccounts()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $UserId = \Aurora\System\Api::getAuthenticatedUserId();
        $aResult = array();
        $mAccounts = $this->oManager->getAccounts($UserId);

        foreach ($mAccounts as $oAccount) {
            if (!$oAccount->issetScope('mail')) {
                $aResult[] = array(
                    'Id' => $oAccount->Id,
                    'UUID' => '', //TODO
                    'Type' => $oAccount->Type,
                    'Email' => $oAccount->Email,
                    'Name' => $oAccount->Name,
                    'Scopes' => $oAccount->Scopes,
                );
            }
        }

        return $aResult;
    }

    /**
     * Deletes oauth account with specified type.
     *
     * @param string $Type Type of oauth account.
     * @return boolean
     */
    public function DeleteAccount($Type, $Email = '')
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $aArgs = [
            'Service' => $Type,
            'Email' => $Email
        ];
        $mResult = false;
        $this->broadcastEvent(
            'ResetAccessToken',
            $aArgs,
            $mResult
        );
        return $this->oManager->deleteAccount(
            \Aurora\System\Api::getAuthenticatedUserId(),
            $Type,
            $Email
        );
    }

    public function GetAccessToken($sType, $sEmail)
    {
        $mResult = false;
        $oAccount = $this->GetAccount($sType, $sEmail);
        if ($oAccount) {
            $aArgs = [
                'Service' => $sType,
                'Account' => $oAccount
            ];
            $this->broadcastEvent(
                'GetAccessToken',
                $aArgs,
                $mResult
            );
        }

        return $mResult;
    }

    public function CreateMailAccount($OAuthAccountData)
    {
        $mResult = false;

        $UserId = \Aurora\Api::getAuthenticatedUserId();
        $FriendlyName = $OAuthAccountData['name'];
        $Email = $OAuthAccountData['email'];
        $IncomingLogin = $OAuthAccountData['email'];

        $IncomingPassword = '';

        if (class_exists('\Aurora\Modules\Mail\Module')) {
            $oMailModuleDecorator = Api::GetModuleDecorator('Mail');
            /** @var \Aurora\Modules\Mail\Module $oMailModuleDecorator */
            if ($oMailModuleDecorator) {
                $mResult = $oMailModuleDecorator->CreateAccount($UserId, $FriendlyName, $Email, $IncomingLogin, $IncomingPassword, null, $OAuthAccountData['type']);

                if ($mResult) {
                    if (class_exists('\Aurora\Modules\Mail\Module')) {
                        $oResException = \Aurora\Modules\Mail\Module::getInstance()->getMailManager()->validateAccountConnection($mResult, false);
                        if ($oResException instanceof \Exception) {
                            $oMailModuleDecorator->DeleteAccount($mResult->Id);
                            throw new \Aurora\System\Exceptions\ApiException(0, $oResException, $this->i18N('ERROR_ACCOUNT_IMAP_VALIDATION_FAILED'));
                        }
                    }
                }
            }
        }

        return $mResult;
    }
    /***** public functions might be called with web API *****/
}