/home/ivoiecob/email.hirewise-va.com/modules/OpenPgpFilesWebclient/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\OpenPgpFilesWebclient;

use Afterlogic\DAV\Server;
use Aurora\Modules\Files\Enums\ErrorCodes;
use Aurora\Modules\Files\Module as FilesModule;
use Aurora\System\Exceptions\ApiException;

/**
 * @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.
 *
 * @property Settings $oModuleSettings
 *
 * @package Modules
 */
class Module extends \Aurora\System\Module\AbstractWebclientModule
{
    protected $aRequireModules = array(
        'Files'
    );

    private $aPublicFileData = null;

    private $aHashes = [];

    /** @var \Aurora\Modules\Files\Module */
    private $oFilesdecorator = null;

    public function init()
    {
        $this->subscribeEvent('FileEntryPub', array($this, 'onFileEntryPub'));
        $this->subscribeEvent('Files::PopulateFileItem::after', array($this, 'onAfterPopulateFileItem'));
        $this->subscribeEvent('Files::CheckUrl', array($this, 'onCheckUrl'), 90);
        $this->subscribeEvent('Files::DeletePublicLink::after', [$this, 'onAfterDeletePublicLink']);
        $this->subscribeEvent('Min::DeleteExpiredHashes::before', [$this, 'onBeforeDeleteExpiredHashes']);
        $this->subscribeEvent('Min::DeleteExpiredHashes::after', [$this, 'onAfterDeleteExpiredHashes']);
        $this->subscribeEvent('Files::GetPublicFiles::before', [$this, 'onBeforeGetPublicFiles']);
        $this->subscribeEvent('System::RunEntry::before', array($this, 'onBeforeRunEntry'));

        $oFilesModule = FilesModule::getInstance();
        if ($oFilesModule) {
            $this->aErrors = [
                ErrorCodes::NotPermitted => $oFilesModule->i18N('INFO_NOTPERMITTED')
            ];
        }

        $this->oFilesdecorator = \Aurora\System\Api::GetModuleDecorator('Files');
    }

    /**
     * @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 function isUrlFileType($sFileName)
    {
        return in_array(pathinfo($sFileName, PATHINFO_EXTENSION), ['url']);
    }

    /***** public functions might be called with web API *****/
    /**
     * Obtains list of module settings for authenticated user.
     *
     * @return array
     */
    public function GetSettings()
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);

        $aSettings = array(
            'EnableSelfDestructingMessages' => $this->oModuleSettings->EnableSelfDestructingMessages,
            'EnablePublicLinkLifetime' => $this->oModuleSettings->EnablePublicLinkLifetime,
        );
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser && $oUser->isNormalOrTenant()) {
            if (null !== $oUser->getExtendedProp(self::GetName() . '::EnableModule')) {
                $aSettings['EnableModule'] = $oUser->getExtendedProp(self::GetName() . '::EnableModule');
            }
        }
        if ($this->aPublicFileData) {
            $aSettings['PublicFileData'] = $this->aPublicFileData;
        }

        return $aSettings;
    }

    public function UpdateSettings($EnableModule)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);

        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser) {
            if ($oUser->isNormalOrTenant()) {
                $oCoreDecorator = \Aurora\Modules\Core\Module::Decorator();
                $oUser->setExtendedProp(self::GetName() . '::EnableModule', $EnableModule);
                return $oCoreDecorator->UpdateUserObject($oUser);
            }
            if ($oUser->Role === \Aurora\System\Enums\UserRole::SuperAdmin) {
                return true;
            }
        }

        return false;
    }

    public function CreatePublicLink($UserId, $Type, $Path, $Name, $Size, $IsFolder, $Password = '', $RecipientEmail = '', $PgpEncryptionMode = '', $LifetimeHrs = 0)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $mResult = [];
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
            if (empty($Type) || empty($Name)) {
                throw new \Aurora\System\Exceptions\ApiException(\Aurora\System\Notifications::InvalidInputParameter);
            }
            $sID = \Aurora\Modules\Min\Module::generateHashId([$oUser->PublicId, $Type, $Path, $Name]);
            $oMin = \Aurora\Modules\Min\Module::getInstance();
            $mMin = $oMin->GetMinByID($sID);
            if (!empty($mMin['__hash__']) && $mMin['UserId'] && $mMin['UserId'] === $oUser->PublicId) {
                $mResult['link'] = '?/files-pub/' . $mMin['__hash__'] . '/list';
                if (!empty($mMin['Password'])) {
                    $mResult['password'] = \Aurora\System\Utils::DecryptValue($mMin['Password']);
                }
            } else {
                $oNode = Server::getNodeForPath('files/' . $Type . '/' . $Path . '/' . $Name);
                if ($oNode instanceof \Afterlogic\DAV\FS\Shared\Directory) {
                    throw new ApiException(ErrorCodes::NotPermitted);
                }
                $aProps = [
                    'UserId' => $oUser->PublicId,
                    'Type' => $Type,
                    'Path' => $Path,
                    'Name' => $Name,
                    'Size' => $Size,
                    'IsFolder' => $IsFolder
                ];
                if (!empty($Password)) {
                    $aProps['Password'] = \Aurora\System\Utils::EncryptValue($Password);
                }
                if (!empty($RecipientEmail)) {
                    $aProps['RecipientEmail'] = $RecipientEmail;
                }
                if (!empty($PgpEncryptionMode)) {
                    $aProps['PgpEncryptionMode'] = $PgpEncryptionMode;
                }
                if ($LifetimeHrs === 0) {
                    $sHash = $oMin->createMin(
                        $sID,
                        $aProps,
                        $oUser->Id
                    );
                } else {
                    $iExpireDate = time() + ((int) $LifetimeHrs * 60 * 60);
                    $sHash = $oMin->createMin(
                        $sID,
                        $aProps,
                        $oUser->Id,
                        $iExpireDate
                    );
                }
                $mMin = $oMin->GetMinByHash($sHash);
                if (!empty($mMin['__hash__'])) {
                    $mResult['link'] = '?/files-pub/' . $mMin['__hash__'] . '/list';
                    if (!empty($mMin['Password'])) {
                        $mResult['password'] = \Aurora\System\Utils::DecryptValue($mMin['Password']);
                    }
                }
            }
        }

        return $mResult;
    }

    public function CreateSelfDestrucPublicLink($UserId, $Subject, $Data, $RecipientEmail, $PgpEncryptionMode, $LifetimeHrs)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        $mResult = [];
        $oUser = \Aurora\System\Api::getAuthenticatedUser();
        if ($oUser instanceof \Aurora\Modules\Core\Models\User) {
            $sID = \Aurora\Modules\Min\Module::generateHashId([$oUser->PublicId, $Subject, $Data]);
            $oMin = \Aurora\Modules\Min\Module::getInstance();
            $mMin = $oMin->GetMinByID($sID);
            if (!empty($mMin['__hash__'])) {
                $mResult['link'] = '?/files-pub/' . $mMin['__hash__'] . '/list';
            } else {
                $aProps = [
                    'UserId' => $oUser->PublicId,
                    'Subject' => $Subject,
                    'Data' => $Data,
                    'RecipientEmail' => $RecipientEmail,
                    'PgpEncryptionMode' => $PgpEncryptionMode
                ];
                $iExpireDate = time() + ((int) $LifetimeHrs * 60 * 60);
                $sHash = $oMin->createMin(
                    $sID,
                    $aProps,
                    $oUser->Id,
                    $iExpireDate
                );
                $mMin = $oMin->GetMinByHash($sHash);
                if (!empty($mMin['__hash__'])) {
                    $mResult['link'] = '?/files-pub/' . $mMin['__hash__'] . '/list';
                }
            }
        }

        return $mResult;
    }

    public function ValidatePublicLinkPassword($UserId, $Hash, $Password)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::Anonymous);
        $bResult = false;
        $oMin = \Aurora\Modules\Min\Module::getInstance();
        $mMin = $oMin->GetMinByHash($Hash);
        if ($mMin && isset($mMin['Password']) && \Aurora\System\Utils::DecryptValue($mMin['Password']) === $Password) {
            $bResult = true;
        }

        return $bResult;
    }

    /***** public functions might be called with web API *****/

    public function onFileEntryPub(&$aData, &$mResult)
    {
        if ($aData && isset($aData['UserId'])) {
            if (isset($aData['ExpireDate'])) {
                $iExpireDate = (int) $aData['ExpireDate'];
                if ($iExpireDate > 0 && time() > $iExpireDate) {
                    $oModuleManager = \Aurora\System\Api::GetModuleManager();
                    $sTheme = $oModuleManager->getModuleConfigValue('CoreWebclient', 'Theme');
                    $sResult = \file_get_contents($this->GetPath() . '/templates/Expired.html');
                    $mResult = \strtr($sResult, array(
                        '{{Expired}}' => $this->i18N('HINT_MESSAGE_LINK_EXPIRED'),
                        '{{Theme}}' => $sTheme,
                    ));
                    return;
                }
            }

            $bLinkOrFile = isset($aData['IsFolder']) && !$aData['IsFolder'] && isset($aData['Name']) && isset($aData['Type']) && isset($aData['Path']);
            $bSelfDestructingEncryptedMessage = isset($aData['Subject']) && isset($aData['Data']) && isset($aData['PgpEncryptionMode']) && isset($aData['RecipientEmail']);
            if ($bLinkOrFile || $bSelfDestructingEncryptedMessage) {
                $bIsUrlFile = isset($aData['Name']) ? $this->isUrlFileType($aData['Name']) : false;

                /** @var \Aurora\Modules\Core\Module $oCoreDecorator */
                $oCoreDecorator = \Aurora\System\Api::GetModuleDecorator('Core');
                $oUser = $oCoreDecorator->GetUserByPublicId($aData['UserId']);
                if ($oUser) {
                    $bPrevState = \Aurora\System\Api::skipCheckUserRole(true);

                    $aCurSession = \Aurora\System\Api::GetUserSession();
                    \Aurora\System\Api::SetUserSession([
                        'UserId' => $oUser->Id
                    ]);

                    $sType = isset($aData['Type']) ? $aData['Type'] : '';
                    $sPath = isset($aData['Path']) ? $aData['Path'] : '';
                    $sName = isset($aData['Name']) ? $aData['Name'] : '';

                    $aFileInfo = $this->oFilesdecorator->GetFileInfo($aData['UserId'], $sType, $sPath, $sName);

                    \Aurora\System\Api::SetUserSession($aCurSession);

                    \Aurora\System\Api::skipCheckUserRole($bPrevState);
                    $bIsEncyptedFile = $aFileInfo
                        && isset($aFileInfo->ExtendedProps)
                        && isset($aFileInfo->ExtendedProps['ParanoidKeyPublic']);
                    $bIsSetPgpEncryptionMode = isset($aData['PgpEncryptionMode']);
                    $oApiIntegrator = \Aurora\System\Managers\Integrator::getInstance();

                    if ($oApiIntegrator
                        && (($bIsEncyptedFile && $bIsSetPgpEncryptionMode)
                            || !$bIsEncyptedFile)
                    ) {
                        $oCoreClientModule = \Aurora\System\Api::GetModule('CoreWebclient');
                        if ($oCoreClientModule instanceof \Aurora\System\Module\AbstractModule) {
                            $sResult = \file_get_contents($oCoreClientModule->GetPath() . '/templates/Index.html');
                            if (\is_string($sResult)) {
                                $oSettings = &\Aurora\System\Api::GetSettings();
                                $sFrameOptions = $oSettings->XFrameOptions;
                                if (0 < \strlen($sFrameOptions)) {
                                    @\header('X-Frame-Options: ' . $sFrameOptions);
                                }
                                $aConfig = [
                                    'public_app' => true,
                                    'modules_list' => array_merge(
                                        $oApiIntegrator->GetModulesForEntry('OpenPgpFilesWebclient'),
                                        $oApiIntegrator->GetModulesForEntry('OpenPgpWebclient')
                                    )
                                ];
                                //passing data to AppData throughGetSettings. GetSettings will be called in $oApiIntegrator->buildBody
                                /** @var \Aurora\Modules\FilesWebclient\Module $oFilesWebclientModule */
                                $oFilesWebclientModule = \Aurora\System\Api::GetModule('FilesWebclient');
                                if ($oFilesWebclientModule) {
                                    $sUrl = (bool) $oFilesWebclientModule->oModuleSettings->ServerUseUrlRewrite ? '/download/' : '?/files-pub/';
                                    $this->aPublicFileData = [
                                        'Url'	=> $sUrl . $aData['__hash__'],
                                        'Hash'	=> $aData['__hash__']
                                    ];
                                    if ($bSelfDestructingEncryptedMessage) {
                                        $this->aPublicFileData['Subject'] =  $aData['Subject'];
                                        $this->aPublicFileData['Data'] =  $aData['Data'];
                                        $this->aPublicFileData['PgpEncryptionMode'] =  $aData['PgpEncryptionMode'];
                                        $this->aPublicFileData['RecipientEmail'] =  $aData['RecipientEmail'];
                                        $this->aPublicFileData['ExpireDate'] = isset($aData['ExpireDate']) ? $aData['ExpireDate'] : null;
                                    } elseif ($bIsEncyptedFile) {
                                        $this->aPublicFileData['PgpEncryptionMode'] = $aData['PgpEncryptionMode'];
                                        $this->aPublicFileData['PgpEncryptionRecipientEmail'] = isset($aData['RecipientEmail']) ? $aData['RecipientEmail'] : '';
                                        $this->aPublicFileData['Size'] =  \Aurora\System\Utils::GetFriendlySize($aData['Size']);
                                        $this->aPublicFileData['Name'] =  $aData['Name'];
                                        $this->aPublicFileData['ParanoidKeyPublic'] = $aFileInfo->ExtendedProps['ParanoidKeyPublic'];
                                        $this->aPublicFileData['InitializationVector'] = $aFileInfo->ExtendedProps['InitializationVector'];
                                    } elseif ($bIsUrlFile) {
                                        $mFile = $this->oFilesdecorator->getRawFileData($aData['UserId'], $aData['Type'], $aData['Path'], $aData['Name'], $aData['__hash__'], 'view');
                                        if (\is_resource($mFile)) {
                                            $mFile = \stream_get_contents($mFile);
                                        }
                                        $aUrlFileInfo = \Aurora\System\Utils::parseIniString($mFile);
                                        if ($aUrlFileInfo && isset($aUrlFileInfo['URL'])) {
                                            $sUrl = $aUrlFileInfo['URL'];
                                            $sFileName = basename($sUrl);
                                            $sFileExtension = \Aurora\System\Utils::GetFileExtension($sFileName);
                                            if (\strtolower($sFileExtension) === 'm3u8') {
                                                $this->aPublicFileData['Url'] = $sUrl;
                                                $this->aPublicFileData['Name'] =  $sFileName; #$aData['Name'];
                                                $this->aPublicFileData['IsSecuredLink'] = isset($aData['Password']);
                                                $this->aPublicFileData['IsUrlFile'] =  true;
                                            }
                                        }
                                    } else {//encrypted link
                                        $this->aPublicFileData['Size'] =  \Aurora\System\Utils::GetFriendlySize($aData['Size']);
                                        $this->aPublicFileData['Name'] =  $aData['Name'];
                                        $this->aPublicFileData['IsSecuredLink'] = isset($aData['Password']);
                                        $this->aPublicFileData['ExpireDate'] = isset($aData['ExpireDate']) ? $aData['ExpireDate'] : null;
                                    }

                                    $mResult = \strtr(
                                        $sResult,
                                        [
                                            '{{AppVersion}}' => \Aurora\Api::Version(),
                                            '{{IntegratorDir}}' => $oApiIntegrator->isRtl() ? 'rtl' : 'ltr',
                                            '{{IntegratorLinks}}' => $oApiIntegrator->buildHeadersLink(),
                                            '{{IntegratorBody}}' => $oApiIntegrator->buildBody($aConfig)
                                        ]
                                    );
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    public function onAfterPopulateFileItem($aArgs, &$oItem)
    {
        \Aurora\System\Api::checkUserRoleIsAtLeast(\Aurora\System\Enums\UserRole::NormalUser);
        if (isset($aArgs['UserId']) &&
            $oItem instanceof \Aurora\Modules\Files\Classes\FileItem
            && isset($oItem->TypeStr)
        ) {
            $oUser = \Aurora\System\Api::getUserById($aArgs['UserId']);
            $iAuthenticatedUserId = \Aurora\System\Api::getAuthenticatedUserId();
            if ($oUser instanceof \Aurora\Modules\Core\Models\User
                && $iAuthenticatedUserId === $aArgs['UserId']
            ) {
                $sID = \Aurora\Modules\Min\Module::generateHashId([$oUser->PublicId, $oItem->TypeStr, $oItem->Path, $oItem->Name]);
                $mMin = \Aurora\Modules\Min\Module::getInstance()->GetMinByID($sID);
                if (!empty($mMin['__hash__'])) {
                    $aExtendedProps = array_merge($oItem->ExtendedProps, [
                        'PasswordForSharing'			=> !empty($mMin['Password']) ? \Aurora\System\Utils::DecryptValue($mMin['Password']) : '',
                        'PublicLink'					=> '?/files-pub/' . $mMin['__hash__'] . '/list',
                        'PublicLinkPgpEncryptionMode'	=> isset($mMin['PgpEncryptionMode']) ? $mMin['PgpEncryptionMode'] : '',
                    ]);
                    $oItem->ExtendedProps = $aExtendedProps;
                }
            }
        }
    }

    public function onCheckUrl($aArgs, &$mResult)
    {
        if (!empty($aArgs['Url'])) {
            $sUrl = $aArgs['Url'];
            if ($sUrl) {
                $sFileName = basename($sUrl);
                $sFileExtension = \Aurora\System\Utils::GetFileExtension($sFileName);
                if (\strtolower($sFileExtension) === 'm3u8') {
                    $mResult['Name'] = $sFileName;
                    return true;
                }
            }
        }
    }

    public function onAfterDeletePublicLink(&$aArgs, &$mResult)
    {
        \Aurora\Modules\Files\Module::Decorator()->UpdateExtendedProps(
            $aArgs['UserId'],
            $aArgs['Type'],
            $aArgs['Path'],
            $aArgs['Name'],
            ['ParanoidKeyPublic' => null]
        );
    }

    public function onBeforeDeleteExpiredHashes(&$aArgs, &$mResult)
    {
        $this->aHashes = [];
        if (isset($aArgs['Time']) && $aArgs['Time'] > 0) {
            $this->aHashes = \Aurora\Modules\Min\Models\MinHash::whereNotNull('ExpireDate')->where('ExpireDate', '<=', $aArgs['Time'])->get()->all();
        }
    }

    public function onAfterDeleteExpiredHashes(&$aArgs, &$mResult)
    {
        foreach ($this->aHashes as $hash) {
            $data = \json_decode($hash['Data'], true);
            if (isset($data['UserId'], $data['Type'], $data['Path'], $data['Name'])) {
                \Aurora\Modules\Files\Module::Decorator()->UpdateExtendedProps(
                    $data['UserId'],
                    $data['Type'],
                    $data['Path'],
                    $data['Name'],
                    ['ParanoidKeyPublic' => null]
                );
            }
        }
        $this->aHashes = [];
    }

    public function onBeforeGetPublicFiles(&$aArgs, &$mResult)
    {
        /** @var \Aurora\Modules\Min\Module $oMinDecorator */
        $oMinDecorator =  \Aurora\Api::GetModuleDecorator('Min');
        if ($oMinDecorator) {
            $mMin = $oMinDecorator->GetMinByHash($aArgs['Hash']);
            if (!empty($mMin['__hash__'])) {
                if (isset($mMin['ExpireDate'])) {
                    $iExpireDate = (int) $mMin['ExpireDate'];
                    if ($iExpireDate > 0 && time() > $iExpireDate) {
                        $mResult = false;
                        return true;
                    }
                }
            }
        }
    }

    public function onBeforeRunEntry(&$aArgs, &$mResult)
    {
        if (isset($aArgs['EntryName']) && strtolower($aArgs['EntryName']) === 'download-file') {
            $sHash = (string) \Aurora\System\Router::getItemByIndex(1, '');
            $aValues = \Aurora\System\Api::DecodeKeyValues($sHash);
            if (isset($aValues['PublicHash'])) {
                /** @var \Aurora\Modules\Min\Module $oMinDecorator */
                $oMinDecorator =  \Aurora\Api::GetModuleDecorator('Min');
                if ($oMinDecorator) {
                    $mMin = $oMinDecorator->GetMinByHash($aValues['PublicHash']);
                    if (!empty($mMin['__hash__'])) {
                        if (isset($mMin['ExpireDate'])) {
                            $iExpireDate = (int) $mMin['ExpireDate'];
                            if ($iExpireDate > 0 && time() > $iExpireDate) {
                                $this->oHttp->StatusHeader(403);
                                exit();
                            }
                        }
                    }
                }
            }
        }
    }
}