diff --git a/dev/Common/Plugins.js b/dev/Common/Plugins.js index 648519ee5..eef1a08ac 100644 --- a/dev/Common/Plugins.js +++ b/dev/Common/Plugins.js @@ -1,5 +1,7 @@ import { settingsAddViewModel } from 'Screen/AbstractSettings'; import { SettingsGet } from 'Common/Globals'; +import { showScreenPopup } from 'Knoin/Knoin'; +import { AbstractViewPopup } from 'Knoin/AbstractViews'; const USER_VIEW_MODELS_HOOKS = [], ADMIN_VIEW_MODELS_HOOKS = []; @@ -53,3 +55,6 @@ rl.pluginSettingsGet = (pluginSection, name) => { plugins = plugins && null != plugins[pluginSection] ? plugins[pluginSection] : null; return plugins ? (null == plugins[name] ? null : plugins[name]) : null; }; + +rl.showPluginPopup = showScreenPopup; +rl.pluginPopupView = AbstractViewPopup; diff --git a/plugins/two-factor-auth/LICENSE b/plugins/two-factor-auth/LICENSE new file mode 100644 index 000000000..5b03ee9bd --- /dev/null +++ b/plugins/two-factor-auth/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) + +Copyright (c) 2021 SnappyMail 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. diff --git a/plugins/two-factor-auth/index.php b/plugins/two-factor-auth/index.php new file mode 100644 index 000000000..2212b3f05 --- /dev/null +++ b/plugins/two-factor-auth/index.php @@ -0,0 +1,381 @@ +UseLangs(true); + + $this->addJs('js/TwoFactorAuthSettings.js'); + + $this->addJsonHook('GetTwoFactorInfo', 'DoGetTwoFactorInfo'); + $this->addJsonHook('CreateTwoFactorSecret', 'DoCreateTwoFactorSecret'); + $this->addJsonHook('ShowTwoFactorSecret', 'DoShowTwoFactorSecret'); + $this->addJsonHook('EnableTwoFactor', 'DoEnableTwoFactor'); + $this->addJsonHook('TestTwoFactorInfo', 'DoTestTwoFactorInfo'); + $this->addJsonHook('ClearTwoFactorInfo', 'DoClearTwoFactorInfo'); + + $this->addTemplate('templates/TwoFactorAuthSettings.html'); + $this->addTemplate('templates/PopupsTwoFactorAuthTest.html'); + } + + public function configMapping() : array + { + return [ + \RainLoop\Plugins\Property::NewInstance('allow_two_factor_auth') + ->SetLabel('TAB_SECURITY/LABEL_ALLOW_TWO_STEP') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL), + \RainLoop\Plugins\Property::NewInstance('force_two_factor_auth') + ->SetLabel('TAB_SECURITY/LABEL_FORCE_TWO_STEP') + ->SetType(\RainLoop\Enumerations\PluginPropertyType::BOOL) + ]; + } + + + public function DoGetTwoFactorInfo() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider() || + !$this->GetCapa(false, static::Capa_TWO_FACTOR, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + return $this->DefaultResponse(__FUNCTION__, + $this->getTwoFactorInfo($oAccount, true)); + } + + public function DoCreateTwoFactorSecret() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider() || + !$this->GetCapa(false, static::Capa_TWO_FACTOR, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $sEmail = $oAccount->ParentEmailHelper(); + + $sSecret = $this->TwoFactorAuthProvider()->CreateSecret(); + + $aCodes = array(); + for ($iIndex = 9; $iIndex > 0; $iIndex--) + { + $aCodes[] = \rand(100000000, 900000000); + } + + $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', + \RainLoop\Utils::EncodeKeyValues(array( + 'User' => $sEmail, + 'Enable' => false, + 'Secret' => $sSecret, + 'BackupCodes' => \implode(' ', $aCodes) + )) + ); + + $this->requestSleep(); + + return $this->DefaultResponse(__FUNCTION__, + $this->getTwoFactorInfo($oAccount)); + } + + public function DoShowTwoFactorSecret() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider() || + !$this->GetCapa(false, static::Capa_TWO_FACTOR, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $aResult = $this->getTwoFactorInfo($oAccount); + unset($aResult['BackupCodes']); + + return $this->DefaultResponse(__FUNCTION__, $aResult); + } + + public function DoEnableTwoFactor() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider() || + !$this->GetCapa(false, static::Capa_TWO_FACTOR, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + +// $this->setSettingsFromParams($oSettings, 'EnableTwoFactor', 'bool'); + + $sEmail = $oAccount->ParentEmailHelper(); + + $bResult = false; + $mData = $this->getTwoFactorInfo($oAccount); + if (isset($mData['Secret'], $mData['BackupCodes'])) + { + $bResult = $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', + \RainLoop\Utils::EncodeKeyValues(array( + 'User' => $sEmail, + 'Enable' => '1' === \trim($this->jsonParam('Enable', '0')), + 'Secret' => $mData['Secret'], + 'BackupCodes' => $mData['BackupCodes'] + )) + ); + } + + return $this->DefaultResponse(__FUNCTION__, $bResult); + } + + public function DoTestTwoFactorInfo() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider() || + !$this->GetCapa(false, static::Capa_TWO_FACTOR, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $sCode = \trim($this->jsonParam('Code', '')); + + $aData = $this->getTwoFactorInfo($oAccount); + $sSecret = !empty($aData['Secret']) ? $aData['Secret'] : ''; + +// $this->Logger()->WriteDump(array( +// $sCode, $sSecret, $aData, +// $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode) +// )); + + $this->requestSleep(); + + return $this->DefaultResponse(__FUNCTION__, + $this->TwoFactorAuthProvider()->VerifyCode($sSecret, $sCode)); + } + + public function DoClearTwoFactorInfo() : array + { + $oAccount = $this->getAccountFromToken(); + + if (!$this->TwoFactorAuthProvider() || + !$this->GetCapa(false, static::Capa_TWO_FACTOR, $oAccount)) + { + return $this->FalseResponse(__FUNCTION__); + } + + $this->StorageProvider()->Clear($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' + ); + + return $this->DefaultResponse(__FUNCTION__, + $this->getTwoFactorInfo($oAccount, true)); + } + + protected function TwoFactorAuthProvider() : \RainLoop\Providers\TwoFactorAuth + { +// if ($this->Config()->Get('plugin', 'allow_two_factor_auth', 0)) +// if ($this->Config()->Get('plugin', 'force_two_factor_auth', 0)) + + if (!$this->oTwoFactorAuthProvider) { + require __DIR__ . '/providers/interface.php'; + require __DIR__ . '/providers/totp.php'; + $this->oTwoFactorAuthProvider = new TwoFactorAuthTotp(); + } + return $this->oTwoFactorAuthProvider; + } + + protected function getTwoFactorInfo(\RainLoop\Model\Account $oAccount, bool $bRemoveSecret = false) : array + { + $sEmail = $oAccount->ParentEmailHelper(); + + $mData = null; + + $aResult = array( + 'User' => '', + 'IsSet' => false, + 'Enable' => false, + 'Secret' => '', + 'UrlTitle' => '', + 'BackupCodes' => '' + ); + + if (!empty($sEmail)) + { + $aResult['User'] = $sEmail; + + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' + ); + + if ($sData) + { + $mData = \RainLoop\Utils::DecodeKeyValues($sData); + } + } + + if (!empty($aResult['User']) && + !empty($mData['User']) && !empty($mData['Secret']) && + !empty($mData['BackupCodes']) && $sEmail === $mData['User']) + { + $aResult['IsSet'] = true; + $aResult['Enable'] = isset($mData['Enable']) ? !!$mData['Enable'] : false; + $aResult['Secret'] = $mData['Secret']; + $aResult['BackupCodes'] = $mData['BackupCodes']; + $aResult['UrlTitle'] = $this->Config()->Get('webmail', 'title', ''); + } + + if ($bRemoveSecret) + { + if (isset($aResult['Secret'])) + { + unset($aResult['Secret']); + } + + if (isset($aResult['UrlTitle'])) + { + unset($aResult['UrlTitle']); + } + + if (isset($aResult['BackupCodes'])) + { + unset($aResult['BackupCodes']); + } + } + + return $aResult; + } + + protected function removeBackupCodeFromTwoFactorInfo(\RainLoop\Model\Account $oAccount, string $sCode) : bool + { + if (!$oAccount || empty($sCode)) + { + return false; + } + + $sData = $this->StorageProvider()->Get($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor' + ); + + if ($sData) + { + $mData = \RainLoop\Utils::DecodeKeyValues($sData); + + if (!empty($mData['BackupCodes'])) + { + $sBackupCodes = \preg_replace('/[^\d]+/', ' ', ' '.$mData['BackupCodes'].' '); + $sBackupCodes = \str_replace(' '.$sCode.' ', '', $sBackupCodes); + + $mData['BackupCodes'] = \trim(\preg_replace('/[^\d]+/', ' ', $sBackupCodes)); + + return $this->StorageProvider()->Put($oAccount, + \RainLoop\Providers\Storage\Enumerations\StorageType::CONFIG, + 'two_factor', + \RainLoop\Utils::EncodeKeyValues($mData) + ); + } + } + + return false; + } + +/* + public function ChangePassword() + { + $oActions = $this->Manager()->Actions(); + $oAccount = $oActions->GetAccount(); + + if (!$oAccount->Email()) { + \trigger_error('ChangePassword failed: empty email address'); + throw new ClientException(static::CouldNotSaveNewPassword); + } + + $sPrevPassword = $this->jsonParam('PrevPassword'); + if ($sPrevPassword !== $oAccount->Password()) { + throw new ClientException(static::CurrentPasswordIncorrect, null, $oActions->StaticI18N('NOTIFICATIONS/CURRENT_PASSWORD_INCORRECT')); + } + + $sNewPassword = $this->jsonParam('NewPassword'); + if ($this->Config()->Get('plugin', 'pass_min_length', 10) > \strlen($sNewPassword)) { + throw new ClientException(static::NewPasswordShort, null, $oActions->StaticI18N('NOTIFICATIONS/NEW_PASSWORD_SHORT')); + } + + if ($this->Config()->Get('plugin', 'pass_min_strength', 70) > static::PasswordStrength($sNewPassword)) { + throw new ClientException(static::NewPasswordWeak, null, $oActions->StaticI18N('NOTIFICATIONS/NEW_PASSWORD_WEAK')); + } + + $bResult = false; + $oConfig = $this->Config(); + foreach ($this->getSupportedDrivers() as $name => $class) { + $sFoundedValue = ''; + if (\RainLoop\Plugins\Helper::ValidateWildcardValues($oAccount->Email(), $oConfig->Get('plugin', "driver_{$name}_allowed_emails"), $sFoundedValue)) { + $name = $class::NAME; + $oLogger = $oActions->Logger(); + try + { + $oDriver = new $class( + $oConfig, + $oLogger + ); + if (!$oDriver->ChangePassword($oAccount, $sPrevPassword, $sNewPassword)) { + throw new ClientException(static::CouldNotSaveNewPassword); + } + $bResult = true; + if ($oLogger) { + $oLogger->Write("{$name} password changed for {$oAccount->Email()}"); + } + } + catch (\Throwable $oException) + { + \trigger_error("{$class} failed: {$oException->getMessage()}"); + if ($oLogger) { + $oLogger->Write("ERROR: {$name} password change for {$oAccount->Email()} failed"); + $oLogger->WriteException($oException); +// $oLogger->WriteException($oException, \MailSo\Log\Enumerations\Type::WARNING, $name); + } + } + } + } + + if (!$bResult) { + \trigger_error("ChangePassword failed"); + throw new ClientException(static::CouldNotSaveNewPassword); + } + + $oAccount->SetPassword($sNewPassword); + $oActions->SetAuthToken($oAccount); + + return $this->jsonResponse(__FUNCTION__, $oActions->GetSpecAuthToken()); + } +*/ +} diff --git a/plugins/two-factor-auth/js/TwoFactorAuthSettings.js b/plugins/two-factor-auth/js/TwoFactorAuthSettings.js new file mode 100644 index 000000000..d51ad57ba --- /dev/null +++ b/plugins/two-factor-auth/js/TwoFactorAuthSettings.js @@ -0,0 +1,296 @@ +/* +import { trigger as translatorTrigger } from 'Common/Translator'; +*/ + +(rl => { if (rl) { + +const + Capa = { + TwoFactor: 'TWO_FACTOR', + TwoFactorForce: 'TWO_FACTOR_FORCE', + }, + + pString = value => null != value ? '' + value : '', + + Remote = new class { + /** + * @param {?Function} fCallback + */ + getTwoFactor(fCallback) { + rl.pluginRemoteRequest(fCallback, 'GetTwoFactorInfo'); + } + + /** + * @param {?Function} fCallback + */ + createTwoFactor(fCallback) { + rl.pluginRemoteRequest(fCallback, 'CreateTwoFactorSecret'); + } + + /** + * @param {?Function} fCallback + */ + clearTwoFactor(fCallback) { + rl.pluginRemoteRequest(fCallback, 'ClearTwoFactorInfo'); + } + + /** + * @param {?Function} fCallback + */ + showTwoFactorSecret(fCallback) { + rl.pluginRemoteRequest(fCallback, 'ShowTwoFactorSecret'); + } + + /** + * @param {?Function} fCallback + * @param {string} sCode + */ + testTwoFactor(fCallback, sCode) { + rl.pluginRemoteRequest(fCallback, 'TestTwoFactorInfo', { + Code: sCode + }); + } + + /** + * @param {?Function} fCallback + * @param {boolean} bEnable + */ + enableTwoFactor(fCallback, bEnable) { + rl.pluginRemoteRequest(fCallback, 'EnableTwoFactor', { + Enable: bEnable ? 1 : 0 + }); + } + + /** + * @param {?Function} fCallback + */ + clearTwoFactorInfo(fCallback) { + rl.pluginRemoteRequest(fCallback, 'ClearTwoFactorInfo'); + } + }; + +class TwoFactorAuthSettings +{ + + constructor() { + this.lock = ko.observable(false); + + this.processing = ko.observable(false); + this.clearing = ko.observable(false); + this.secreting = ko.observable(false); + + this.viewUser = ko.observable(''); + this.twoFactorStatus = ko.observable(false); + + this.twoFactorTested = ko.observable(false); + + this.viewSecret = ko.observable(''); + this.viewBackupCodes = ko.observable(''); + this.viewUrlTitle = ko.observable(''); + this.viewUrl = ko.observable(''); + + this.viewEnable_ = ko.observable(false); + + this.capaTwoFactor = rl.settings.capa(Capa.TwoFactor); + + const fn = iError => iError && this.viewEnable_(false); + this.addComputables({ + viewEnable: { + read: this.viewEnable_, + write: (value) => { + value = !!value; + if (value && this.twoFactorTested()) { + this.viewEnable_(value); + Remote.enableTwoFactor(fn, value); + } else { + if (!value) { + this.viewEnable_(value); + } + Remote.enableTwoFactor(fn, false); + } + } + }, + + viewTwoFactorEnableTooltip: () => { +// translatorTrigger(); + return this.twoFactorTested() || this.viewEnable_() + ? '' + : rl.i18n('POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_TEST_BEFORE_DESC'); + }, + + viewTwoFactorStatus: () => { +// translatorTrigger(); + return rl.i18n('POPUPS_TWO_FACTOR_CFG/TWO_FACTOR_SECRET_' + + (this.twoFactorStatus() ? '' : 'NOT_') + + 'CONFIGURED_DESC' + ); + }, + + twoFactorAllowedEnable: () => this.viewEnable() || this.twoFactorTested() + }); + + this.onResult = this.onResult.bind(this); + this.onShowSecretResult = this.onShowSecretResult.bind(this); + } + + showSecret() { + this.secreting(true); + Remote.showTwoFactorSecret(this.onShowSecretResult); + } + + hideSecret() { + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrlTitle(''); + this.viewUrl(''); + } + + createTwoFactor() { + this.processing(true); + Remote.createTwoFactor(this.onResult); + } + + logout() { + rl.app.logout(); + } + + testTwoFactor() { + rl.showPluginPopup(TwoFactorAuthTestPopupView, [this.twoFactorTested]); + } + + clearTwoFactor() { + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrlTitle(''); + this.viewUrl(''); + + this.twoFactorTested(false); + + this.clearing(true); + Remote.clearTwoFactor(this.onResult); + } + + onShow(bLock) { + this.lock(!!bLock); + + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrlTitle(''); + this.viewUrl(''); + } + + onHide() { + if (this.lock()) { + location.reload(); + } + } + + getQr() { + return 'otpauth://totp/' + encodeURIComponent(this.viewUser()) + + '?secret=' + encodeURIComponent(this.viewSecret()) + + '&issuer=' + encodeURIComponent(''); + } + + onResult(iError, oData) { + this.processing(false); + this.clearing(false); + + if (iError) { + this.viewUser(''); + this.viewEnable_(false); + this.twoFactorStatus(false); + this.twoFactorTested(false); + + this.viewSecret(''); + this.viewBackupCodes(''); + this.viewUrlTitle(''); + this.viewUrl(''); + } else { + this.viewUser(pString(oData.Result.User)); + this.viewEnable_(!!oData.Result.Enable); + this.twoFactorStatus(!!oData.Result.IsSet); + this.twoFactorTested(!!oData.Result.Tested); + + this.viewSecret(pString(oData.Result.Secret)); + this.viewBackupCodes(pString(oData.Result.BackupCodes).replace(/[\s]+/g, ' ')); + + this.viewUrlTitle(pString(oData.Result.UrlTitle)); + this.viewUrl(qr.toDataURL({ level: 'M', size: 8, value: this.getQr() })); + } + } + + onShowSecretResult(iError, data) { + this.secreting(false); + + if (iError) { + this.viewSecret(''); + this.viewUrlTitle(''); + this.viewUrl(''); + } else { + this.viewSecret(pString(data.Result.Secret)); + this.viewUrlTitle(pString(data.Result.UrlTitle)); + this.viewUrl(qr.toDataURL({ level: 'M', size: 6, value: this.getQr() })); + } + } + + onBuild() { + if (this.capaTwoFactor) { + this.processing(true); + Remote.getTwoFactor(this.onResult); + } + } +} + +class TwoFactorAuthTestPopupView extends rl.pluginPopupView { + constructor() { + super('TwoFactorAuthTest'); + + this.addObservables({ + code: '', + codeStatus: null, + + testing: false + }); + + this.koTestedTrigger = null; + + ko.decorateCommands(this, { + testCodeCommand: self => self.code() && !self.testing() + }); + } + + testCodeCommand() { + this.testing(true); + Remote.testTwoFactor(iError => { + this.testing(false); + this.codeStatus(!iError); + + if (this.koTestedTrigger && this.codeStatus()) { + this.koTestedTrigger(true); + } + }, this.code()); + } + + clearPopup() { + this.code(''); + this.codeStatus(null); + this.testing(false); + + this.koTestedTrigger = null; + } + + onShow(koTestedTrigger) { + this.clearPopup(); + + this.koTestedTrigger = koTestedTrigger; + } +} + +rl.addSettingsViewModel( + TwoFactorAuthSettings, + 'TwoFactorAuthSettings', + 'POPUPS_TWO_FACTOR_CFG/LEGEND_TWO_FACTOR_AUTH', + 'two-factor-auth' +); + +}})(window.rl); diff --git a/plugins/two-factor-auth/langs/de_DE.ini b/plugins/two-factor-auth/langs/de_DE.ini new file mode 100644 index 000000000..367e8a324 --- /dev/null +++ b/plugins/two-factor-auth/langs/de_DE.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "Zwei-Faktor-Authentifizierung erlauben" +LABEL_FORCE_TWO_STEP = "Zwei-Faktor-Authentifizierung erzwingen" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "Zwei-Faktor-Authentifizierung" +LABEL_ENABLE_TWO_FACTOR = "Zwei-Faktor-Authentifizierung aktivieren" +LABEL_TWO_FACTOR_USER = "Benutzer" +LABEL_TWO_FACTOR_STATUS = "Status" +LABEL_TWO_FACTOR_SECRET = "Geheimnis" +LABEL_TWO_FACTOR_BACKUP_CODES = "Sicherungscodes" +BUTTON_CREATE = "Neues Geheimnis erstellen" +BUTTON_ACTIVATE = "Aktivieren" +LINK_TEST = "test" +BUTTON_SHOW_SECRET = "Geheimnis einblenden" +BUTTON_HIDE_SECRET = "Geheminis ausblenden" +TWO_FACTOR_REQUIRE_DESC = "Ihr Benutzerkonto erfordert die Einrichtung der Zwei-Faktor-Authentifizierung." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Konfiguriert" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Nicht konfiguriert" +TWO_FACTOR_SECRET_DESC = "Importieren Sie diese Information in Ihre Google-Authenticator-Anwendung (oder andere TOTP-Anwendung), indem Sie den unten bereitgestellten QR-Code verwenden oder den Code manuell eingeben." +TWO_FACTOR_BACKUP_CODES_DESC = "Sollten Sie keine Codes über den Google Authenticator erhalten, können Sie einen Sicherungscode zur Anmeldung verwenden. Der Sicherungscode wird inaktiv, sobald Sie ihn zur Anmeldung verwendet haben." +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "Sie können diese Einstellung nicht ohne vorherigen Test verändern." +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "Zwei-Faktor-Authentifizierung" +LABEL_CODE = "Code" +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "Zwei-Faktor-Authentifizierung konfigurieren" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Zwei-Faktor-Authentifizierung erforderlich" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Fehler bei Zwei-Faktor-Authentifizierung" +[LOGIN] +LABEL_VERIFICATION_CODE = "Verifizierungscode" +LABEL_DONT_ASK_VERIFICATION_CODE = "Für zwei Wochen nicht nach dem Code fragen" diff --git a/plugins/two-factor-auth/langs/en.ini b/plugins/two-factor-auth/langs/en.ini new file mode 100644 index 000000000..ab46a74f4 --- /dev/null +++ b/plugins/two-factor-auth/langs/en.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "Allow 2-Step Verification" +LABEL_FORCE_TWO_STEP = "Enforce 2-Step Verification" +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "2-Step verification test" +LABEL_CODE = "Code" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "2-Step Verification (TOTP)" +LABEL_ENABLE_TWO_FACTOR = "Enable 2-Step verification" +LABEL_TWO_FACTOR_USER = "User" +LABEL_TWO_FACTOR_STATUS = "Status" +LABEL_TWO_FACTOR_SECRET = "Secret" +LABEL_TWO_FACTOR_BACKUP_CODES = "Backup codes" +BUTTON_CREATE = "Create a secret" +BUTTON_ACTIVATE = "Activate" +LINK_TEST = "test" +BUTTON_SHOW_SECRET = "Show Secret" +BUTTON_HIDE_SECRET = "Hide Secret" +TWO_FACTOR_REQUIRE_DESC = "Your account requires 2-Step verification configuration." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Configured" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Not configured" +TWO_FACTOR_SECRET_DESC = "Import this info into your Google Authenticator client (or other TOTP client) using the provided QR code below or by entering the code manually.\n" +TWO_FACTOR_BACKUP_CODES_DESC = "If you can't receive codes via Google Authenticator (or other TOTP client), you can use backup codes to sign in. After you’ve used a backup code to sign in, it will become inactive.\n" +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "You can't change this setting before test." +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "Configure 2-Step verification" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Two factor verification required" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Two factor verification error" +[LOGIN] +LABEL_VERIFICATION_CODE = "Verification Code" +LABEL_DONT_ASK_VERIFICATION_CODE = "Don't ask for the code for 2 weeks" diff --git a/plugins/two-factor-auth/langs/es_ES.ini b/plugins/two-factor-auth/langs/es_ES.ini new file mode 100644 index 000000000..5e8e4ebac --- /dev/null +++ b/plugins/two-factor-auth/langs/es_ES.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "Activar la verificación de 2 pasos" +LABEL_FORCE_TWO_STEP = "Forzar la Autenticación en 2 pasos" +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "Prueba de verificación de 2 pasos" +LABEL_CODE = "Código" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "Verificación de 2 Pasos" +LABEL_ENABLE_TWO_FACTOR = "Activar la verificación de 2 pasos" +LABEL_TWO_FACTOR_USER = "Usuario" +LABEL_TWO_FACTOR_STATUS = "Estado" +LABEL_TWO_FACTOR_SECRET = "Clave secreta" +LABEL_TWO_FACTOR_BACKUP_CODES = "Códigos de copia de seguridad" +BUTTON_CREATE = "Crear nueva clave secreta" +BUTTON_ACTIVATE = "Activate" +LINK_TEST = "probar" +BUTTON_SHOW_SECRET = "Mostrar clave secreta" +BUTTON_HIDE_SECRET = "Ocultar clave secreta" +TWO_FACTOR_REQUIRE_DESC = "Se requiere que configure la verificación de 2-pasos." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Configurado" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "No configurado" +TWO_FACTOR_SECRET_DESC = "Importar esta información en su cliente Google Authenticator (u otro cliente TOTP) utilizando el código QR se indica debajo o introduciendo el código manualmente." +TWO_FACTOR_BACKUP_CODES_DESC = "Si usted no puede recibir los códigos a través de Google Authenticator, puede utilizar códigos de copia de seguridad para firmar pulg Después de que usted ha utilizado un código de copia de seguridad para iniciar sesión, se convertirá en inactiva." +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "You can't change this setting before test." +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "Configurar verificación de 2-Pasos" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Factor de verificación en dos pasos requerido" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Error de verificación en dos pasos" +[LOGIN] +LABEL_VERIFICATION_CODE = "Código de verificación" +LABEL_DONT_ASK_VERIFICATION_CODE = "No solicitar el código de verificación durante 2 semanas" diff --git a/plugins/two-factor-auth/langs/fr_FR.ini b/plugins/two-factor-auth/langs/fr_FR.ini new file mode 100644 index 000000000..0e1f580a7 --- /dev/null +++ b/plugins/two-factor-auth/langs/fr_FR.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "Autoriser l'authentification en deux étapes" +LABEL_FORCE_TWO_STEP = "Forcer l'authentification en deux étapes" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "Authentification en deux étapes" +LABEL_ENABLE_TWO_FACTOR = "Activer l'authentification en deux étapes" +LABEL_TWO_FACTOR_USER = "Utilisateur" +LABEL_TWO_FACTOR_STATUS = "Statut" +LABEL_TWO_FACTOR_SECRET = "Secret" +LABEL_TWO_FACTOR_BACKUP_CODES = "Code de sauvegarde" +BUTTON_CREATE = "Créer une nouvelle clé secrète" +BUTTON_ACTIVATE = "Activer" +LINK_TEST = "test" +BUTTON_SHOW_SECRET = "Montrer la clé secrète" +BUTTON_HIDE_SECRET = "Masquer la clé secrète" +TWO_FACTOR_REQUIRE_DESC = "Votre compte requiert la configuration d'une vérification en deux étapes." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Configuré" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Non configuré" +TWO_FACTOR_SECRET_DESC = "Importez ces informations dans votre client Google Authenticator (ou un autre client TOTP) en utilisant le code QR fourni ci-dessous ou en entrant les valeurs manuellement." +TWO_FACTOR_BACKUP_CODES_DESC = "Si vous ne pouvez pas recevoir les codes par Google Authenticator, vous pouvez utiliser les codes de sauvegarde pour vous connecter. Après avoir fait cela, il deviendra inactif." +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "Vous ne pouvez pas modifier ce paramètre avant de l'avoir essayé." +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "Test d'authentification en deux étapes" +LABEL_CODE = "Code" +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "Configurer l'authentification en deux étapes" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Double authentification requise" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Erreur lors de la double authentification" +[LOGIN] +LABEL_VERIFICATION_CODE = "Code de vérification" +LABEL_DONT_ASK_VERIFICATION_CODE = "Ne plus demander le code pendant 2 semaines" diff --git a/plugins/two-factor-auth/langs/hu-HU.ini b/plugins/two-factor-auth/langs/hu-HU.ini new file mode 100644 index 000000000..e60ed1daf --- /dev/null +++ b/plugins/two-factor-auth/langs/hu-HU.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "2 lépcsős hitelesítés engedélyezése" +LABEL_FORCE_TWO_STEP = "2 lépcsős hitelesítés kényszerítése" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "2 lépcsős hitelesítés" +LABEL_ENABLE_TWO_FACTOR = "2 lépcsős hitelesítés engedélyezése" +LABEL_TWO_FACTOR_USER = "Felhasználó" +LABEL_TWO_FACTOR_STATUS = "Állapot" +LABEL_TWO_FACTOR_SECRET = "Titok" +LABEL_TWO_FACTOR_BACKUP_CODES = "Biztonsági kódok" +BUTTON_CREATE = "Új titok létrehozás" +BUTTON_ACTIVATE = "Aktivál" +LINK_TEST = "teszt" +BUTTON_SHOW_SECRET = "Titok megjelenítése" +BUTTON_HIDE_SECRET = "Titok elrejtése" +TWO_FACTOR_REQUIRE_DESC = "A fiókhoz 2 lépcsős hitelesítés szükséges." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Beállítva" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Nincs beállítva" +TWO_FACTOR_SECRET_DESC = "Importáld ezt az infót a Google Authenticator kliensedbe (vagy más TOTP kliensbe) az alábbi QR kód használatával vagy a kód manuális megadatásával.\n" +TWO_FACTOR_BACKUP_CODES_DESC = "Ha nem kapod meg a kódokat a Google Authenticator kliensből (vagy más TOTP kliensből), akkor a bejelentkezéshez használhatod a biztonsági kódot. A biztonsági kód használata után inaktívvá válik.\n" +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "Tesztelés nélkül nem lehet megváltoztatni ezt a beállítást." +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "2-lépéses hitelesítés teszt" +LABEL_CODE = "Kód" +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "2 lépcsős hitelesítés beállítása" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Kétlépcsős azonosítás kötelező" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Kétlépcsős azonosítás hiba" +[LOGIN] +LABEL_VERIFICATION_CODE = "Megerősítő kód" +LABEL_DONT_ASK_VERIFICATION_CODE = "Két hétig ne kérje a kódot" diff --git a/plugins/two-factor-auth/langs/nl_NL.ini b/plugins/two-factor-auth/langs/nl_NL.ini new file mode 100644 index 000000000..52c2fcce9 --- /dev/null +++ b/plugins/two-factor-auth/langs/nl_NL.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "2-Stap verificatie toestaan" +LABEL_FORCE_TWO_STEP = "2-Stap verificatie afdwingen" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "2-Stap verificatie" +LABEL_ENABLE_TWO_FACTOR = "Gebruik 2-Stap verificatie" +LABEL_TWO_FACTOR_USER = "Gebruikersnaam" +LABEL_TWO_FACTOR_STATUS = "Status" +LABEL_TWO_FACTOR_SECRET = "Geheime sleutel" +LABEL_TWO_FACTOR_BACKUP_CODES = "Backup codes" +BUTTON_CREATE = "Nieuwe geheime sleutel aanmaken" +BUTTON_ACTIVATE = "Activate" +LINK_TEST = "test" +BUTTON_SHOW_SECRET = "Bekijk geheime sleutel" +BUTTON_HIDE_SECRET = "Verberg geheime sleutel" +TWO_FACTOR_REQUIRE_DESC = "Uw account vereist 2-Stap verificatie configuratie." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Geconfigureerd" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Niet geconfigureerd" +TWO_FACTOR_SECRET_DESC = "Importeer deze informatie in uw Google Authenticator-client (of andere TOTP-client) door gebruik te maken van de QR code hier beneden of door de code handmatig in te voeren." +TWO_FACTOR_BACKUP_CODES_DESC = "Als u geen codes ontvangt via de Google Authenticator kunt u de backup codes gebruiken om in te loggen. Na gebruik van de backup code wordt deze inactief." +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "U kunt 2-stap verificatie niet activeren voordat u het succesvol getest heeft." +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "2-Stap verificatie test" +LABEL_CODE = "Code" +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "Configureer 2-stap verificatie" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "2-Stap verificatie vereist" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "2-Stap verificatie fout" +[LOGIN] +LABEL_VERIFICATION_CODE = "Verificatie Code" +LABEL_DONT_ASK_VERIFICATION_CODE = "Vraag 2 weken niet naar code" diff --git a/plugins/two-factor-auth/langs/sv-SE.ini b/plugins/two-factor-auth/langs/sv-SE.ini new file mode 100644 index 000000000..101aed016 --- /dev/null +++ b/plugins/two-factor-auth/langs/sv-SE.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "Låt 2-tvåstegsverifiering" +LABEL_FORCE_TWO_STEP = "Driva 2-tvåstegsverifiering" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "Tvåstegsverifiering (TOTP)" +LABEL_ENABLE_TWO_FACTOR = "Aktivera tvåstegsverifiering" +LABEL_TWO_FACTOR_USER = "Användare" +LABEL_TWO_FACTOR_STATUS = "Status" +LABEL_TWO_FACTOR_SECRET = "Hemlig kod" +LABEL_TWO_FACTOR_BACKUP_CODES = "Backupkoder" +BUTTON_CREATE = "Skapa ny hemlig kod" +BUTTON_ACTIVATE = "Aktivera" +LINK_TEST = "test" +BUTTON_SHOW_SECRET = "Visa hemlig kod" +BUTTON_HIDE_SECRET = "Göm hemlig kod" +TWO_FACTOR_REQUIRE_DESC = "Ditt konto kräver tvåstegsverifiering." +TWO_FACTOR_SECRET_CONFIGURED_DESC = "Konfigurerad" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "Inte konfigurarad" +TWO_FACTOR_SECRET_DESC = "Importera denna information till din Google Authenticator klient (eller annan TOTP klient) med denna QR kod eller manuellt med koden här nedan." +TWO_FACTOR_BACKUP_CODES_DESC = "Om du inte kan ta emot koderna med Google Authenticator, så använd backupkoderna för att logga in. När du använt backupkoderna för att logga in så blir dom inaktiva." +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "Du kan inte ändra denna inställning innan test." +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "Tvåstegsverifieringstest" +LABEL_CODE = "Kod" +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "Konfigurera tvåstegsverifiering" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "Tvåstegsverifiering krävs" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "Tvåstegsverifieringsfel" +[LOGIN] +LABEL_VERIFICATION_CODE = "Verifikationskod" +LABEL_DONT_ASK_VERIFICATION_CODE = "Fråga inte efter koden på 2 veckor" diff --git a/plugins/two-factor-auth/langs/zh_CN.ini b/plugins/two-factor-auth/langs/zh_CN.ini new file mode 100644 index 000000000..5395a41d8 --- /dev/null +++ b/plugins/two-factor-auth/langs/zh_CN.ini @@ -0,0 +1,32 @@ +[TAB_SECURITY] +LABEL_ALLOW_TWO_STEP = "允许两步验证" +LABEL_FORCE_TWO_STEP = "强制使用两步验证" +[POPUPS_TWO_FACTOR_CFG] +LEGEND_TWO_FACTOR_AUTH = "两步验证 (TOTP)" +LABEL_ENABLE_TWO_FACTOR = "启用两步验证" +LABEL_TWO_FACTOR_USER = "用户" +LABEL_TWO_FACTOR_STATUS = "状态" +LABEL_TWO_FACTOR_SECRET = "密钥" +LABEL_TWO_FACTOR_BACKUP_CODES = "备用代码" +BUTTON_CREATE = "创建新密钥" +BUTTON_ACTIVATE = "启用" +LINK_TEST = "测试" +BUTTON_SHOW_SECRET = "显示密钥" +BUTTON_HIDE_SECRET = "隐藏密钥" +TWO_FACTOR_REQUIRE_DESC = "您的账户需要设置两步验证。" +TWO_FACTOR_SECRET_CONFIGURED_DESC = "已设置" +TWO_FACTOR_SECRET_NOT_CONFIGURED_DESC = "未设置" +TWO_FACTOR_SECRET_DESC = "将下面的信息导入 Google Authenticator (或其他 TOTP 客户端)。 扫描下面的二维码或手动输入密钥。\n" +TWO_FACTOR_BACKUP_CODES_DESC = "如果你无法从验证器获取验证码,你可以使用备用代码登录。 当你使用过某一个备用代码后,它将会失效。\n" +TWO_FACTOR_SECRET_TEST_BEFORE_DESC = "完成测试前你无法更改此设置。" +[POPUPS_TWO_FACTOR_TEST] +TITLE_TEST_CODE = "两步验证测试" +LABEL_CODE = "代码" +[SETTINGS_SECURITY] +LABEL_CONFIGURE_TWO_FACTOR = "配置两步验证" +[NOTIFICATIONS] +ACCOUNT_TWO_FACTOR_AUTH_REQUIRED = "需要进行两步验证" +ACCOUNT_TWO_FACTOR_AUTH_ERROR = "两步验证错误" +[LOGIN] +LABEL_VERIFICATION_CODE = "验证码" +LABEL_DONT_ASK_VERIFICATION_CODE = "在两周内不再询问验证码" diff --git a/plugins/two-factor-auth/providers/interface.php b/plugins/two-factor-auth/providers/interface.php new file mode 100644 index 000000000..678a382c6 --- /dev/null +++ b/plugins/two-factor-auth/providers/interface.php @@ -0,0 +1,8 @@ + 0, // ord 65 + 'B' => 1, + 'C' => 2, + 'D' => 3, + 'E' => 4, + 'F' => 5, + 'G' => 6, + 'H' => 7, + 'I' => 8, + 'J' => 9, + 'K' => 10, + 'L' => 11, + 'M' => 12, + 'N' => 13, + 'O' => 14, + 'P' => 15, + 'Q' => 16, + 'R' => 17, + 'S' => 18, + 'T' => 19, + 'U' => 20, + 'V' => 21, + 'W' => 22, + 'X' => 23, + 'Y' => 24, + 'Z' => 25, // ord 90 + '2' => 26, // ord 50 + '3' => 27, + '4' => 28, + '5' => 29, + '6' => 30, + '7' => 31 // ord 55 + ); + + protected static function Base32Decode(string $data) + { + $data = \strtoupper(\rtrim($data, "=\x20\t\n\r\0\x0B")); + $dataSize = \strlen($data); + $buf = 0; + $bufSize = 0; + $res = ''; + for ($i = 0; $i < $dataSize; ++$i) { + $c = $data[$i]; + if (isset(static::$map[$c])) { + $buf = ($buf << 5) | static::$map[$c]; + $bufSize += 5; + if ($bufSize > 7) { + $bufSize -= 8; + $res .= \chr(($buf & (0xff << $bufSize)) >> $bufSize); + } + } + } + return $res; + } + +} diff --git a/plugins/two-factor-auth/templates/PopupsTwoFactorAuthTest.html b/plugins/two-factor-auth/templates/PopupsTwoFactorAuthTest.html new file mode 100644 index 000000000..bb3c48c76 --- /dev/null +++ b/plugins/two-factor-auth/templates/PopupsTwoFactorAuthTest.html @@ -0,0 +1,29 @@ +
+ ++
+ ++ +
+ ++