mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-11-03 19:08:50 +03:00
More Nextcloud changes #2055
This commit is contained in:
parent
62c20c8327
commit
c4f2033368
10 changed files with 228 additions and 30 deletions
|
|
@ -28,13 +28,15 @@
|
|||
* Custom 'data' folder path
|
||||
*/
|
||||
if (class_exists('OC')) {
|
||||
define('APP_DATA_FOLDER_PATH', \OCA\SnappyMail\Util\SnappyMailHelper::getAppDataFolder());
|
||||
/*
|
||||
define('APP_DATA_FOLDER_PATH', \rtrim(\trim(
|
||||
// \OCA\SnappyMail\Util\SnappyMailHelper::getSystemConfigValue('datadirectory', '')
|
||||
\method_exists(\OC::$server, 'getSystemConfig')
|
||||
? \OC::$server->getSystemConfig()->getValue('datadirectory', '')
|
||||
// Nextcloud 34+
|
||||
: \OC::$server->get(\OCP\IConfig::class)->getSystemValue('datadirectory', '')
|
||||
), '\\/') . '/appdata_snappymail/');
|
||||
*/
|
||||
} else {
|
||||
http_response_code(400);
|
||||
header($_SERVER['SERVER_PROTOCOL'].' 400 Bad Request', true, 400);
|
||||
|
|
|
|||
|
|
@ -68,7 +68,9 @@ There, click on the link to go to the SnappyMail admin panel.
|
|||
<nextcloud min-version="25" />
|
||||
</dependencies>
|
||||
<settings>
|
||||
<admin-section>OCA\SnappyMail\Settings\Section</admin-section>
|
||||
<admin>OCA\SnappyMail\Settings\AdminSettings</admin>
|
||||
<personal-section>OCA\SnappyMail\Settings\Section</personal-section>
|
||||
<personal>OCA\SnappyMail\Settings\PersonalSettings</personal>
|
||||
</settings>
|
||||
<navigations>
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ class Application extends App implements IBootstrap
|
|||
{
|
||||
// SnappyMailHelper::$server = $context->getServerContainer();
|
||||
|
||||
if (!\is_dir(\rtrim(\trim(SnappyMailHelper::getSystemConfigValue('datadirectory', '')), '\\/') . '/appdata_snappymail')) {
|
||||
if (!\is_dir(SnappyMailHelper::getAppDataFolder())) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,17 +10,20 @@ use OCP\AppFramework\Http\JSONResponse;
|
|||
use OCP\IConfig;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
//use OCP\IUserSession;
|
||||
|
||||
class FetchController extends Controller
|
||||
{
|
||||
private IConfig $config;
|
||||
private IL10N $l10n;
|
||||
// private IUserSession $userSession;
|
||||
|
||||
public function __construct(string $appName, IRequest $request, \Psr\Container\ContainerInterface $container)
|
||||
{
|
||||
parent::__construct($appName, $request);
|
||||
$this->config = $container->query(IConfig::class);
|
||||
$this->l10n = $container->query(IL10N::class);
|
||||
// $this->userSession = $container->query(IUserSession::class);
|
||||
}
|
||||
|
||||
public function upgrade(): JSONResponse
|
||||
|
|
@ -34,7 +37,7 @@ class FetchController extends Controller
|
|||
'Message' => $this->l10n->t('Upgraded successfully')
|
||||
]);
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
$error .= ': ' . $e->getMessage();
|
||||
}
|
||||
return new JSONResponse([
|
||||
|
|
@ -91,7 +94,7 @@ class FetchController extends Controller
|
|||
'status' => 'success',
|
||||
'Message' => $this->l10n->t('Saved successfully')
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
return new JSONResponse([
|
||||
'status' => 'error',
|
||||
'Message' => $e->getMessage()
|
||||
|
|
@ -136,7 +139,7 @@ class FetchController extends Controller
|
|||
'Message' => $this->l10n->t('Saved successfully'),
|
||||
'Email' => $sEmail
|
||||
]);
|
||||
} catch (Exception $e) {
|
||||
} catch (\Throwable $e) {
|
||||
// Logout as the credentials might have changed, as exception could be in one attribute
|
||||
// TODO: Handle both exceptions separately?
|
||||
SnappyMailHelper::loadApp();
|
||||
|
|
|
|||
|
|
@ -33,13 +33,18 @@ class PageController extends Controller
|
|||
$bAdmin = false;
|
||||
if (!empty($_SERVER['QUERY_STRING'])) {
|
||||
SnappyMailHelper::loadApp();
|
||||
$bAdmin = \RainLoop\Api::Config()->Get('security', 'admin_panel_key', 'admin') == $_SERVER['QUERY_STRING'];
|
||||
$bAdmin = $this->getAdminPanelKey() == $_SERVER['QUERY_STRING'];
|
||||
if ($this->isAdminServiceRequest()) {
|
||||
$this->ensureNextcloudAdminSession();
|
||||
}
|
||||
if (!$bAdmin) {
|
||||
return SnappyMailHelper::startApp(true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$bAdmin && SnappyMailHelper::getAppConfigValue('snappymail-no-embed')) {
|
||||
if (!$bAdmin && $this->config->getAppValue('snappymail', 'snappymail-no-embed')) {
|
||||
// if (!$bAdmin && SnappyMailHelper::getAppConfigValue('snappymail-no-embed')) {
|
||||
// SnappyMailHelper::getNavigationManager()->setActiveEntry('snappymail');
|
||||
$this->navigationManager->setActiveEntry('snappymail');
|
||||
\OCP\Util::addScript('snappymail', 'snappymail');
|
||||
\OCP\Util::addStyle('snappymail', 'style');
|
||||
|
|
@ -51,10 +56,119 @@ class PageController extends Controller
|
|||
$csp = new ContentSecurityPolicy();
|
||||
$csp->addAllowedFrameDomain("'self'");
|
||||
// $csp->addAllowedFrameAncestorDomain("'self'");
|
||||
$response->setContentSecurityPolicy($csp);
|
||||
$response->setContentSecurityPolicy($csp->getPolicy());
|
||||
return $response;
|
||||
}
|
||||
|
||||
return $this->renderEmbeddedApp($bAdmin);
|
||||
}
|
||||
|
||||
/**
|
||||
* Integrated administration entry point.
|
||||
*
|
||||
* Unlike the legacy ?admin URL this route is protected by Nextcloud's
|
||||
* default admin middleware. No @NoAdminRequired annotation must be added.
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function admin()
|
||||
{
|
||||
$this->ensureNextcloudAdminSession();
|
||||
|
||||
if (!empty($_SERVER['QUERY_STRING'])) {
|
||||
return $this->handleIntegratedAdminRequest();
|
||||
}
|
||||
|
||||
return $this->renderEmbeddedApp(true, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* SnappyMail uses its own X-SM-Token for the legacy admin JSON API. The
|
||||
* Nextcloud CSRF middleware cannot validate that token, therefore this
|
||||
* endpoint opts out of the Nextcloud request token check. It remains admin
|
||||
* protected and SnappyMail validates X-SM-Token for every modifying call.
|
||||
*
|
||||
* @NoCSRFRequired
|
||||
*/
|
||||
public function adminPost()
|
||||
{
|
||||
$this->ensureNextcloudAdminSession();
|
||||
return $this->handleIntegratedAdminRequest();
|
||||
}
|
||||
|
||||
private function ensureNextcloudAdminSession(): void
|
||||
{
|
||||
$user = \OC::$server->get(\OCP\IUserSession::class)->getUser();
|
||||
if (!$user || !\OC_User::isAdminUser($user->getUID())) {
|
||||
throw new \RuntimeException('SnappyMail administration requires a Nextcloud administrator session.');
|
||||
}
|
||||
|
||||
SnappyMailHelper::loadApp();
|
||||
$config = \RainLoop\Api::Config();
|
||||
if (!$config->Get('security', 'allow_admin_panel', true)) {
|
||||
throw new \RuntimeException('SnappyMail administration is disabled.');
|
||||
}
|
||||
|
||||
$actions = \RainLoop\Api::Actions();
|
||||
if (!$actions->IsAdminLoggined(false)) {
|
||||
$random = \MailSo\Base\Utils::Sha1Rand();
|
||||
if (!$actions->Cacher(null, true)->Set(\RainLoop\KeyPathHelper::SessionAdminKey($random), \time())) {
|
||||
throw new \RuntimeException('Could not create the SnappyMail administrator session.');
|
||||
}
|
||||
|
||||
$token = \RainLoop\Utils::EncodeKeyValuesQ(['token', $random]);
|
||||
if (!$token) {
|
||||
throw new \RuntimeException('Could not encode the SnappyMail administrator session.');
|
||||
}
|
||||
\SnappyMail\Cookies::set('smadmin', $token);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleIntegratedAdminRequest(): void
|
||||
{
|
||||
$adminKey = $this->getAdminPanelKey();
|
||||
$query = \trim((string) ($_SERVER['QUERY_STRING'] ?? ''), " \t\n\r\0\x0B/");
|
||||
// The SnappyMail admin client already includes its configured admin
|
||||
// path in JSON and asset requests. Prefix only requests that do not
|
||||
// contain it yet; otherwise "admin/admin/Json" is produced and domain
|
||||
// as well as extension requests are routed to a non-existing service.
|
||||
if ($query !== $adminKey && 0 !== \strpos($query, $adminKey . '/')) {
|
||||
$query = $adminKey . ($query ? '/' . $query : '');
|
||||
}
|
||||
$_SERVER['QUERY_STRING'] = $query;
|
||||
$_GET[$adminKey] = '1';
|
||||
|
||||
SnappyMailHelper::startApp(true);
|
||||
}
|
||||
|
||||
private function getAdminPanelKey(): string
|
||||
{
|
||||
$key = (string) \RainLoop\Api::Config()->Get('admin_panel', 'key', '');
|
||||
return '' !== $key ? $key : 'admin';
|
||||
}
|
||||
|
||||
private function isAdminServiceRequest(): bool
|
||||
{
|
||||
$query = \trim((string) ($_SERVER['QUERY_STRING'] ?? ''), " \t\n\r\0\x0B/");
|
||||
$ampersand = \strpos($query, '&');
|
||||
if (false !== $ampersand) {
|
||||
$query = \substr($query, 0, $ampersand);
|
||||
}
|
||||
$adminKey = $this->getAdminPanelKey();
|
||||
return $query === $adminKey || 0 === \strpos($query, $adminKey . '/');
|
||||
}
|
||||
|
||||
private function protectLegacyAdminRequest(): void
|
||||
{
|
||||
SnappyMailHelper::loadApp();
|
||||
if ($this->isAdminServiceRequest()) {
|
||||
$this->ensureNextcloudAdminSession();
|
||||
}
|
||||
}
|
||||
|
||||
private function renderEmbeddedApp(bool $bAdmin, bool $blank = false)
|
||||
{
|
||||
// SnappyMailHelper::getNavigationManager()->setActiveEntry('snappymail');
|
||||
$this->navigationManager->setActiveEntry('snappymail');
|
||||
|
||||
\OCP\Util::addStyle('snappymail', 'embed');
|
||||
|
|
@ -93,9 +207,9 @@ class PageController extends Controller
|
|||
\OCP\Util::addHeader('link', ['type'=>'text/css','rel'=>'stylesheet','href'=>\RainLoop\Utils::WebStaticPath('css/'.($bAdmin?'admin':'app').$sAppCssMin.'.css')], '');
|
||||
// \OCP\Util::addHeader('style', ['id'=>'app-theme-style','data-href'=>$params['BaseAppThemeCssLink']], $params['BaseAppThemeCss']);
|
||||
|
||||
$response = new TemplateResponse('snappymail', 'index_embed', $params);
|
||||
$response = new TemplateResponse('snappymail', 'index_embed', $params, $blank ? 'blank' : 'user');
|
||||
|
||||
$response->setContentSecurityPolicy($csp);
|
||||
$response->setContentSecurityPolicy($csp->getPolicy());
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
|
@ -106,6 +220,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function appGet()
|
||||
{
|
||||
$this->protectLegacyAdminRequest();
|
||||
return SnappyMailHelper::startApp(true);
|
||||
}
|
||||
|
||||
|
|
@ -115,6 +230,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function appPost()
|
||||
{
|
||||
$this->protectLegacyAdminRequest();
|
||||
return SnappyMailHelper::startApp(true);
|
||||
}
|
||||
|
||||
|
|
@ -124,6 +240,7 @@ class PageController extends Controller
|
|||
*/
|
||||
public function indexPost()
|
||||
{
|
||||
$this->protectLegacyAdminRequest();
|
||||
return SnappyMailHelper::startApp(true);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ class AdminSettings implements ISettings
|
|||
$parameters['snappymail-admin-password'] = $sPassword;
|
||||
|
||||
$parameters['can-import-rainloop'] = $sPassword && \is_dir(
|
||||
// SnappyMailHelper::getAppDataFolder()
|
||||
\rtrim(\trim($this->config->getSystemValue('datadirectory', '')), '\\/')
|
||||
. '/rainloop-storage'
|
||||
);
|
||||
|
|
@ -98,7 +99,7 @@ class AdminSettings implements ISettings
|
|||
|
||||
public function getSection()
|
||||
{
|
||||
return 'additional';
|
||||
return 'snappymail';
|
||||
}
|
||||
|
||||
public function getPriority()
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ use OCP\Settings\ISettings;
|
|||
|
||||
class PersonalSettings implements ISettings
|
||||
{
|
||||
private $config;
|
||||
private IConfig $config;
|
||||
|
||||
public function __construct(IConfig $config)
|
||||
{
|
||||
|
|
@ -33,7 +33,7 @@ class PersonalSettings implements ISettings
|
|||
|
||||
public function getSection()
|
||||
{
|
||||
return 'additional';
|
||||
return 'snappymail';
|
||||
}
|
||||
|
||||
public function getPriority()
|
||||
|
|
|
|||
38
integrations/nextcloud/snappymail/lib/Settings/Section.php
Normal file
38
integrations/nextcloud/snappymail/lib/Settings/Section.php
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
namespace OCA\SnappyMail\Settings;
|
||||
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Settings\IIconSection;
|
||||
|
||||
class Section implements IIconSection
|
||||
{
|
||||
private IL10N $l;
|
||||
private IURLGenerator $urlGenerator;
|
||||
|
||||
public function __construct(IL10N $l, IURLGenerator $urlGenerator)
|
||||
{
|
||||
$this->l = $l;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
}
|
||||
|
||||
public function getID(): string
|
||||
{
|
||||
return 'snappymail';
|
||||
}
|
||||
|
||||
public function getName(): string
|
||||
{
|
||||
return $this->l->t('SnappyMail');
|
||||
}
|
||||
|
||||
public function getPriority(): int
|
||||
{
|
||||
return 50;
|
||||
}
|
||||
|
||||
public function getIcon(): string
|
||||
{
|
||||
return $this->urlGenerator->imagePath('snappymail', 'favicon-mask.svg');
|
||||
}
|
||||
}
|
||||
|
|
@ -15,9 +15,8 @@ class RainLoop
|
|||
*/
|
||||
public static function import(): array
|
||||
{
|
||||
$dir = \rtrim(\trim(SnappyMailHelper::getSystemConfigValue('datadirectory', '')), '\\/');
|
||||
$dir_snappy = $dir . '/appdata_snappymail/';
|
||||
$dir_rainloop = $dir . '/rainloop-storage';
|
||||
$dir_snappy = SnappyMailHelper::getAppDataFolder();
|
||||
$dir_rainloop = SnappyMailHelper::getDataDirectory() . '/rainloop-storage';
|
||||
$result = [];
|
||||
$rainloop_plugins = [];
|
||||
if (\is_dir($dir_rainloop)) {
|
||||
|
|
|
|||
|
|
@ -34,6 +34,16 @@ class SnappyMailHelper
|
|||
return static::$server || \OC::$server;
|
||||
}
|
||||
|
||||
public static function getAppDataFolder(): string
|
||||
{
|
||||
return static::getDataDirectory() . '/appdata_snappymail/';
|
||||
}
|
||||
|
||||
public static function getDataDirectory(): string
|
||||
{
|
||||
return \rtrim(\trim(static::getSystemConfigValue('datadirectory', '')), '\\/');
|
||||
}
|
||||
|
||||
public static function getConfig(): object
|
||||
{
|
||||
return \method_exists(\OC::$server, 'getConfig')
|
||||
|
|
@ -110,10 +120,26 @@ class SnappyMailHelper
|
|||
}
|
||||
}
|
||||
|
||||
public static function linkToRoute(string $value): string
|
||||
{
|
||||
return (\method_exists(\OC::$server, 'getURLGenerator')
|
||||
? \OC::$server->getURLGenerator()
|
||||
// Nextcloud 34+
|
||||
: \OCP\Server::get(\OCP\IURLGenerator::class)
|
||||
)->linkToRoute($value);
|
||||
}
|
||||
|
||||
public static function loadApp(): void
|
||||
{
|
||||
$app_dir = \dirname(\dirname(__DIR__)) . '/app';
|
||||
|
||||
if (\class_exists('RainLoop\\Api')) {
|
||||
return;
|
||||
$expected = \realpath($app_dir);
|
||||
$actual = \defined('APP_INDEX_ROOT_PATH') ? \realpath(APP_INDEX_ROOT_PATH) : false;
|
||||
if ($expected && $actual && $expected === $actual) {
|
||||
return;
|
||||
}
|
||||
throw new \RuntimeException('SnappyMail cannot start because another SnappyMail-compatible app is already loaded in the same request.');
|
||||
}
|
||||
|
||||
// Nextcloud the default spl_autoload_register() not working
|
||||
|
|
@ -129,9 +155,8 @@ class SnappyMailHelper
|
|||
// define('APP_VERSION', '0.0.0');
|
||||
// define('APP_INDEX_ROOT_PATH', __DIR__ . DIRECTORY_SEPARATOR);
|
||||
// include APP_INDEX_ROOT_PATH.'snappymail/v/'.APP_VERSION.'/include.php';
|
||||
// define('APP_DATA_FOLDER_PATH', \rtrim(\trim(static::getSystemConfigValue('datadirectory', '')), '\\/').'/appdata_snappymail/');
|
||||
// define('APP_DATA_FOLDER_PATH', static::getAppDataFolder());
|
||||
|
||||
$app_dir = \dirname(\dirname(__DIR__)) . '/app';
|
||||
require_once $app_dir . '/index.php';
|
||||
}
|
||||
|
||||
|
|
@ -173,7 +198,7 @@ class SnappyMailHelper
|
|||
if (24 < $OC_Version[0]) {
|
||||
$ocSession = static::getSession();
|
||||
$ocSession->reopen();
|
||||
if (!$doLogin && $ocSession['snappymail-uid'] && $ocSession['snappymail-uid'] != $aCredentials[0]) {
|
||||
if (!$doLogin && $ocSession->get('snappymail-uid') && $ocSession->get('snappymail-uid') != $aCredentials[0]) {
|
||||
// UID changed, Impersonate plugin probably active
|
||||
$oActions->Logout(true);
|
||||
$doLogin = true;
|
||||
|
|
@ -193,9 +218,20 @@ class SnappyMailHelper
|
|||
} catch (\Throwable $e) {
|
||||
// Login failure, reset password to prevent more attempts
|
||||
if (!$isOIDC) {
|
||||
$sUID = static::getUID();
|
||||
static::setSessionValue('snappymail-passphrase', '');
|
||||
static::getConfig()->setUserValue($sUID, 'snappymail', 'passphrase', '');
|
||||
// Credentials saved in the user's personal settings must never be
|
||||
// deleted as a side effect of a failed login. A temporary network,
|
||||
// DNS, TLS, or mail server outage is not a password change.
|
||||
//
|
||||
// Only discard the password captured from the current Nextcloud
|
||||
// session when the IMAP server explicitly rejected authentication.
|
||||
// Persisted credentials remain available for the user to inspect or
|
||||
// replace in their personal settings.
|
||||
if ($e instanceof \RainLoop\Exceptions\ClientException
|
||||
&& \RainLoop\Notifications::AuthError === $e->getCode()
|
||||
) {
|
||||
static::setSessionValue('snappymail-passphrase', '');
|
||||
// static::getConfig()->setUserValue(static::getUID(), 'snappymail', 'passphrase', '');
|
||||
}
|
||||
\SnappyMail\Log::error('Nextcloud', $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
|
@ -261,7 +297,7 @@ class SnappyMailHelper
|
|||
|
||||
// If the current user ID is identical to login ID (not valid when using account switching),
|
||||
// this has the second priority.
|
||||
if ($ocSession['snappymail-nc-uid'] == $sUID) {
|
||||
if ($ocSession->get('snappymail-nc-uid') == $sUID) {
|
||||
|
||||
// If OpenID Connect (OIDC) is enabled and used for login, use this.
|
||||
if (static::isOIDCLogin()) {
|
||||
|
|
@ -276,10 +312,10 @@ class SnappyMailHelper
|
|||
$sPassword = '';
|
||||
if ($config->getAppValue('snappymail', 'snappymail-autologin', false)) {
|
||||
$sEmail = $sUID;
|
||||
$sPassword = $ocSession['snappymail-passphrase'];
|
||||
$sPassword = $ocSession->get('snappymail-passphrase');
|
||||
} else if ($config->getAppValue('snappymail', 'snappymail-autologin-with-email', false)) {
|
||||
$sEmail = $config->getUserValue($sUID, 'settings', 'email');
|
||||
$sPassword = $ocSession['snappymail-passphrase'];
|
||||
$sPassword = $ocSession->get('snappymail-passphrase');
|
||||
} else {
|
||||
\SnappyMail\Log::debug('Nextcloud', 'snappymail-autologin is off');
|
||||
}
|
||||
|
|
@ -287,7 +323,7 @@ class SnappyMailHelper
|
|||
return [$sUID, $sEmail, static::decodePassword($sPassword, $sUID)];
|
||||
}
|
||||
} else {
|
||||
\SnappyMail\Log::debug('Nextcloud', "snappymail-nc-uid mismatch '{$ocSession['snappymail-nc-uid']}' != '{$sUID}'");
|
||||
\SnappyMail\Log::debug('Nextcloud', "snappymail-nc-uid mismatch '{$ocSession->get('snappymail-nc-uid')}' != '{$sUID}'");
|
||||
}
|
||||
|
||||
return [$sUID, '', ''];
|
||||
|
|
@ -295,7 +331,7 @@ class SnappyMailHelper
|
|||
|
||||
public static function getAppUrl(): string
|
||||
{
|
||||
return \OC::$server->getURLGenerator()->linkToRoute('snappymail.page.appGet');
|
||||
return static::linkToRoute('snappymail.page.appGet');
|
||||
}
|
||||
|
||||
public static function normalizeUrl(string $sUrl): string
|
||||
|
|
@ -308,13 +344,13 @@ class SnappyMailHelper
|
|||
return $sUrl;
|
||||
}
|
||||
|
||||
public static function encodePassword(string $sPassword, string $sSalt): string
|
||||
public static function encodePassword(#[\SensitiveParameter] string $sPassword, string $sSalt): string
|
||||
{
|
||||
static::loadApp();
|
||||
return \SnappyMail\Crypt::EncryptUrlSafe($sPassword, $sSalt);
|
||||
}
|
||||
|
||||
public static function decodePassword(string $sPassword, string $sSalt): ?\SnappyMail\SensitiveString
|
||||
public static function decodePassword(#[\SensitiveParameter] string $sPassword, string $sSalt): ?\SnappyMail\SensitiveString
|
||||
{
|
||||
static::loadApp();
|
||||
$result = \SnappyMail\Crypt::DecryptUrlSafe($sPassword, $sSalt);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue