This commit is contained in:
the-djmaze 2023-02-01 10:01:13 +01:00
parent 76ae57b481
commit cc383f6b0e
5 changed files with 142 additions and 76 deletions

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2014 RainLoop Team
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,63 @@
<?php
class LoginOverridePlugin extends \RainLoop\Plugins\AbstractPlugin
{
const
NAME = 'Login Override',
VERSION = '2.0',
RELEASE = '2023-02-01',
REQUIRED = '2.25.2',
CATEGORY = 'Filters',
DESCRIPTION = 'Override IMAP/SMTP login credentials for specific users.';
public function Init() : void
{
$this->addHook('imap.before-login', 'MapImapCredentials');
$this->addHook('smtp.before-login', 'MapSmtpCredentials');
}
public function MapImapCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Imap\ImapClient $oSmtpClient, \MailSo\Imap\Settings $oSettings)
{
static::MapCredentials($oAccount, $oSettings, $this->Config()->getDecrypted('plugin', 'imap_mapping', ''));
}
public function MapSmtpCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Smtp\SmtpClient $oSmtpClient, \MailSo\Smtp\Settings $oSettings)
{
static::MapCredentials($oAccount, $oSettings, $this->Config()->getDecrypted('plugin', 'smtp_mapping', ''));
}
private static function MapCredentials(\RainLoop\Model\Account $oAccount, \MailSo\Net\ConnectSettings $oSettings, string $mapping)
{
$sEmail = $oAccount->Email();
$aList = \preg_split('/\\R/', $mapping);
foreach ($aList as $line) {
$line = \explode(':', $line, 3);
if (!empty($line[0]) && $line[0] === $sEmail) {
if (!empty($line[1])) {
$oSettings->Login = $line[1];
}
if (!empty($line[2])) {
$oSettings->Password = $line[2];
}
}
}
}
protected function configMapping() : array
{
return array(
\RainLoop\Plugins\Property::NewInstance('imap_mapping')
->SetLabel('IMAP mapping')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Each line as email:loginname:password, loginname or password may be empty to use default')
->SetDefaultValue('user@example.com:user1:password1')
->SetEncrypted(),
\RainLoop\Plugins\Property::NewInstance('smtp_mapping')
->SetLabel('SMTP mapping')
->SetType(\RainLoop\Enumerations\PluginPropertyType::STRING_TEXT)
->SetDescription('Each line as email:loginname:password, loginname or password may be empty to use default')
->SetDefaultValue('user@example.com:user1:password1')
->SetEncrypted()
);
}
}

View file

@ -122,12 +122,16 @@ trait AdminExtensions
$aSettings = (array) $this->GetActionParam('settings', []);
foreach ($aMap as $oItem) {
$sKey = $oItem->Name();
$sValue = $aSettings[$sKey] ?? $oConfig->Get('plugin', $sKey);
if (PluginPropertyType::PASSWORD !== $oItem->Type() || static::APP_DUMMY !== $sValue) {
$oItem->SetValue($sValue);
$mResultValue = $oItem->Value();
if (null !== $mResultValue) {
$oConfig->Set('plugin', $sKey, $mResultValue);
$mValue = $aSettings[$sKey] ?? $oConfig->Get('plugin', $sKey);
if (PluginPropertyType::PASSWORD !== $oItem->Type() || static::APP_DUMMY !== $mValue) {
$oItem->SetValue($mValue);
$mValue = $oItem->Value();
if (null !== $mValue) {
if ($oItem->encrypted) {
$oConfig->setEncrypted('plugin', $sKey, $mValue);
} else {
$oConfig->Set('plugin', $sKey, $mValue);
}
}
}
}

View file

@ -4,30 +4,15 @@ namespace RainLoop\Config;
abstract class AbstractConfig implements \JsonSerializable
{
/**
* @var string
*/
private $sFile;
private string $sFile;
/**
* @var string
*/
private $sAdditionalFile = '';
private string $sAdditionalFile = '';
/**
* @var array
*/
private $aData;
private array $aData;
/**
* @var bool
*/
private $bUseApcCache;
private bool $bUseApcCache;
/**
* @var string
*/
private $sFileHeader;
private string $sFileHeader;
public function __construct(string $sFileName, string $sFileHeader = '', string $sAdditionalFileName = '')
{
@ -78,8 +63,7 @@ abstract class AbstractConfig implements \JsonSerializable
*/
public function Set(string $sSectionKey, string $sParamKey, $mParamValue) : void
{
if (isset($this->aData[$sSectionKey][$sParamKey][0]))
{
if (isset($this->aData[$sSectionKey][$sParamKey][0])) {
if (!\is_scalar($mParamValue)) {
$mParamValue = null;
}
@ -99,13 +83,26 @@ abstract class AbstractConfig implements \JsonSerializable
$this->aData[$sSectionKey][$sParamKey][0] = (string) $mParamValue;
break;
}
}
else if ('custom' === $sSectionKey)
{
} else if ('custom' === $sSectionKey) {
$this->aData[$sSectionKey][$sParamKey] = array((string) $mParamValue);
}
}
public function getDecrypted(string $sSection, string $sName, $mDefault = null)
{
// $salt = \basename($this->sFile) not possible due to RainLoop\Plugins\Property
return isset($this->aData[$sSection][$sName][0])
? \SnappyMail\Crypt::DecryptFromJSON($this->aData[$sSection][$sName][0], \APP_SALT)
: $mDefault;
}
public function setEncrypted(string $sSectionKey, string $sParamKey, $mParamValue) : void
{
// $salt = \basename($this->sFile) not possible due to RainLoop\Plugins\Property
$mParamValue = \SnappyMail\Crypt::EncryptToJSON($mParamValue, \APP_SALT);
$this->Set($sSectionKey, $sParamKey, $mParamValue);
}
private function cacheKey() : string
{
return 'config:'.\sha1($this->sFile).':'.\sha1($this->sAdditionalFile).':';
@ -113,24 +110,20 @@ abstract class AbstractConfig implements \JsonSerializable
private function loadDataFromCache() : bool
{
if ($this->bUseApcCache)
{
if ($this->bUseApcCache) {
$iMTime = \filemtime($this->sFile);
$iMTime = \is_int($iMTime) && 0 < $iMTime ? $iMTime : 0;
$iATime = $this->sAdditionalFile ? \filemtime($this->sAdditionalFile) : 0;
$iATime = \is_int($iATime) && 0 < $iATime ? $iATime : 0;
if (0 < $iMTime)
{
if (0 < $iMTime) {
$sKey = $this->cacheKey();
$sTimeHash = \apcu_fetch($sKey.'time');
if ($sTimeHash && $sTimeHash === \md5($iMTime.'/'.$iATime))
{
if ($sTimeHash && $sTimeHash === \md5($iMTime.'/'.$iATime)) {
$aFetchData = \apcu_fetch($sKey.'data');
if (\is_array($aFetchData))
{
if (\is_array($aFetchData)) {
$this->aData = $aFetchData;
return true;
}
@ -143,16 +136,14 @@ abstract class AbstractConfig implements \JsonSerializable
private function storeDataToCache() : bool
{
if ($this->bUseApcCache)
{
if ($this->bUseApcCache) {
$iMTime = \filemtime($this->sFile);
$iMTime = \is_int($iMTime) && 0 < $iMTime ? $iMTime : 0;
$iATime = $this->sAdditionalFile ? \filemtime($this->sAdditionalFile) : 0;
$iATime = \is_int($iATime) && 0 < $iATime ? $iATime : 0;
if (0 < $iMTime)
{
if (0 < $iMTime) {
$sKey = $this->cacheKey();
\apcu_store($sKey.'time', \md5($iMTime.'/'.$iATime));
@ -167,8 +158,7 @@ abstract class AbstractConfig implements \JsonSerializable
private function clearCache() : bool
{
if ($this->bUseApcCache)
{
if ($this->bUseApcCache) {
$sKey = $this->cacheKey();
\apcu_delete($sKey.'time');
@ -186,10 +176,8 @@ abstract class AbstractConfig implements \JsonSerializable
if (!\file_exists($sFile) && \str_ends_with($sFile, '.json')) {
$sFile = \str_replace('.json', '.ini', $sFile);
}
if (\file_exists($sFile) && \is_readable($sFile))
{
if ($this->loadDataFromCache())
{
if (\file_exists($sFile) && \is_readable($sFile)) {
if ($this->loadDataFromCache()) {
return true;
}
@ -198,14 +186,10 @@ abstract class AbstractConfig implements \JsonSerializable
} else {
$aData = \parse_ini_file($sFile, true);
}
if ($aData && \count($aData))
{
foreach ($aData as $sSectionKey => $aSectionValue)
{
if (\is_array($aSectionValue))
{
foreach ($aSectionValue as $sParamKey => $mParamValue)
{
if ($aData && \count($aData)) {
foreach ($aData as $sSectionKey => $aSectionValue) {
if (\is_array($aSectionValue)) {
foreach ($aSectionValue as $sParamKey => $mParamValue) {
$this->Set($sSectionKey, $sParamKey, $mParamValue);
}
}
@ -213,21 +197,16 @@ abstract class AbstractConfig implements \JsonSerializable
unset($aData);
if (\file_exists($this->sAdditionalFile) && \is_readable($this->sAdditionalFile))
{
if (\file_exists($this->sAdditionalFile) && \is_readable($this->sAdditionalFile)) {
if (\str_ends_with($this->sAdditionalFile, '.json')) {
$aData = \json_decode(\file_get_contents($this->sAdditionalFile), true);
} else {
$aData = \parse_ini_file($this->sAdditionalFile, true);
}
if ($aData && \count($aData))
{
foreach ($aData as $sSectionKey => $aSectionValue)
{
if (\is_array($aSectionValue))
{
foreach ($aSectionValue as $sParamKey => $mParamValue)
{
if ($aData && \count($aData)) {
foreach ($aData as $sSectionKey => $aSectionValue) {
if (\is_array($aSectionValue)) {
foreach ($aSectionValue as $sParamKey => $mParamValue) {
$this->Set($sSectionKey, $sParamKey, $mParamValue);
}
}
@ -248,8 +227,7 @@ abstract class AbstractConfig implements \JsonSerializable
public function Save() : bool
{
if (\file_exists($this->sFile) && !\is_writable($this->sFile))
{
if (\file_exists($this->sFile) && !\is_writable($this->sFile)) {
return false;
}

View file

@ -30,6 +30,8 @@ class Property implements \JsonSerializable
private string $sPlaceholder = '';
public bool $encrypted = false;
function __construct(string $sName)
{
$this->sName = $sName;
@ -43,7 +45,6 @@ class Property implements \JsonSerializable
public function SetType(int $iType) : self
{
$this->iType = (int) $iType;
return $this;
}
@ -93,42 +94,42 @@ class Property implements \JsonSerializable
} else {
$this->mDefaultValue = $mDefaultValue;
}
return $this;
}
public function SetOptions(array $aOptions) : self
{
$this->aOptions = $aOptions;
return $this;
}
public function SetPlaceholder(string $sPlaceholder) : self
{
$this->sPlaceholder = $sPlaceholder;
return $this;
}
public function SetLabel(string $sLabel) : self
{
$this->sLabel = $sLabel;
return $this;
}
public function SetDescription(string $sDesc) : self
{
$this->sDesc = $sDesc;
return $this;
}
public function SetAllowedInJs(bool $bValue = true) : self
{
$this->bAllowedInJs = $bValue;
return $this;
}
public function SetEncrypted(bool $bValue = true) : self
{
$this->encrypted = $bValue;
return $this;
}
@ -188,7 +189,7 @@ class Property implements \JsonSerializable
{
return array(
'@Object' => 'Object/PluginProperty',
'value' => $this->mValue,
'value' => $this->encrypted ? \SnappyMail\Crypt::DecryptFromJSON($this->mValue, \APP_SALT) : $this->mValue,
'placeholder' => $this->sPlaceholder,
'name' => $this->sName,
'type' => $this->iType,