diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..6dfa517 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,18 @@ +# MFA + +## 1.0.1 - 2022-12-26 +## Features +- New Galician translation (gl_ES) +## Bugfixes +- Update localazy strings #11027 + +## 1.0.0 +#Features +- New option to select which Auth methods are affected by MFA +- 6-digit OTP Token + +## 0.9.0 (Internal) +### Features +- OTP Auth +- OTP Token sent via e-mail +- Expired tokens clean up Automatic Action diff --git a/front/config.form.php b/front/config.form.php new file mode 100644 index 0000000..12938f0 --- /dev/null +++ b/front/config.form.php @@ -0,0 +1,47 @@ +. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +include('../../../inc/includes.php'); +// Check if plugin is activated... +$plugin = new Plugin(); +if (!$plugin->isInstalled('mfa') || !$plugin->isActivated('mfa')) { + Html::displayNotFoundError(); +} + +Session::checkRight('config', UPDATE); + +$config = new PluginMfaConfig(); +if (isset($_POST["update"])) { + $config->check($_POST['id'], UPDATE); + $config->update($_POST); + Html::back(); +} + +Html::redirect($CFG_GLPI["root_doc"] . "/front/config.form.php?forcetab=" . urlencode('PluginMfaConfig$1')); diff --git a/front/mfa.form.php b/front/mfa.form.php new file mode 100644 index 0000000..64c2397 --- /dev/null +++ b/front/mfa.form.php @@ -0,0 +1,140 @@ +. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +use Glpi\Toolbox\Sanitizer; + +include('../../../inc/includes.php'); + +$_POST = array_map('stripslashes', $_POST); + +if (isset($_POST['code'])) { + $mfa = new PluginMfaMfa(); + if ($mfa->getFromDBByCrit(['code' => $_POST['code']])) { + $auth = new Auth(); + $user = new User(); + $user->getFromDB($mfa->fields['users_id']); + $auth->auth_succeded = true; + $auth->user = $user; + Session::init($auth); + $mfa->delete(['id' => $mfa->getID()]); + Auth::redirectIfAuthenticated(); + } else { + // we have done at least a good login? No, we exit. + Html::nullHeader("Login", $CFG_GLPI["root_doc"] . '/index.php'); + echo '
' . __('Incorrect One-Time Security Code', 'mfa') . '

'; + // Logout whit noAUto to manage auto_login with errors + echo '' . __('Log in again') . '
'; + Html::nullFooter(); + exit(); + } +} else { + + if (!isset($_SESSION["glpicookietest"]) || ($_SESSION["glpicookietest"] != 'testcookie')) { + if (!is_writable(GLPI_SESSION_DIR)) { + Html::redirect($CFG_GLPI['root_doc'] . "/index.php?error=2"); + } else { + Html::redirect($CFG_GLPI['root_doc'] . "/index.php?error=1"); + } + } + //Do login and checks + //$user_present = 1; + if (isset($_SESSION['namfield']) && isset($_POST[$_SESSION['namfield']])) { + $login = $_POST[$_SESSION['namfield']]; + } else { + $login = ''; + } + if (isset($_SESSION['pwdfield']) && isset($_POST[$_SESSION['pwdfield']])) { + $password = Sanitizer::unsanitize($_POST[$_SESSION['pwdfield']]); + } else { + $password = ''; + } + // Manage the selection of the auth source (local, LDAP id, MAIL id) + if (isset($_POST['auth'])) { + $login_auth = $_POST['auth']; + } else { + $login_auth = ''; + } + + $authtype = $login_auth; + if ($login_auth == 'local') { + $authtype = Auth::DB_GLPI; + } else if (preg_match('/^(?ldap|mail|external)-(?\d+)$/', $login_auth, $auth_matches)) { + if ($auth_matches['type'] == 'ldap') { + $authtype = Auth::LDAP; + } else if ($auth_matches['type'] == 'mail') { + $authtype = Auth::MAIL; + } else if ($auth_matches['type'] == 'external') { + $authtype = Auth::EXTERNAL; + } + } + + $remember = isset($_SESSION['rmbfield']) && isset($_POST[$_SESSION['rmbfield']]) && $CFG_GLPI["login_remember_time"]; + + // Redirect management + $REDIRECT = ""; + if (isset($_POST['redirect']) && (strlen($_POST['redirect']) > 0)) { + $REDIRECT = "?redirect=" . rawurlencode($_POST['redirect']); + } else if (isset($_GET['redirect']) && strlen($_GET['redirect']) > 0) { + $REDIRECT = "?redirect=" . rawurlencode($_GET['redirect']); + } + + $auth = new Auth(); + + if ($auth->login($login, $password, (isset($_REQUEST["noAUTO"]) ? $_REQUEST["noAUTO"] : false), $remember, $login_auth)) { + $config = new PluginMfaConfig(); + if (!$config->needCode($authtype)) { + Auth::redirectIfAuthenticated(); + } else { + if (countElementsInTable(PluginMfaMfa::getTable(), ['users_id' => Session::getLoginUserID()]) <= 0) { + $mfa = new PluginMfaMfa(); + $mfa->add(['users_id' => Session::getLoginUserID(), 'code' => PluginMfaMfa::getRandomInt(6)]); + NotificationEvent::raiseEvent('securitycodegenerate', $mfa, ['entities_id' => 0]); + QueuedNotification::forceSendFor($mfa->getType(), $mfa->fields['id']); + } + Session::destroy(); + Auth::setRememberMeCookie(''); + Session::setPath(); + Session::start(); + Session::loadLanguage('', false); + $_SESSION['glpi_use_mode'] = Session::NORMAL_MODE; + Html::nullHeader("Login", $CFG_GLPI["root_doc"] . '/index.php'); + PluginMfaMfa::showCodeForm(); + } + } else { + // we have done at least a good login? No, we exit. + Html::nullHeader("Login", $CFG_GLPI["root_doc"] . '/index.php'); + echo '
' . $auth->getErr() . '

'; + // Logout whit noAUto to manage auto_login with errors + echo '' . __('Log in again') . '
'; + Html::nullFooter(); + exit(); + } +} diff --git a/hook.php b/hook.php new file mode 100644 index 0000000..622ee7a --- /dev/null +++ b/hook.php @@ -0,0 +1,78 @@ +. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +function plugin_mfa_install() +{ + $migration = new Migration(PLUGIN_MFA_VERSION); + + foreach (glob(dirname(__FILE__) . '/inc/*') as $filepath) { + if (preg_match("/inc.(.+)\.class.php/", $filepath, $matches)) { + $classname = 'PluginMfa' . ucfirst($matches[1]); + include_once($filepath); + if (method_exists($classname, 'install')) { + $classname::install($migration); + } + } + } + $migration->executeMigration(); + + return true; +} + +function plugin_mfa_uninstall() +{ + $migration = new Migration(PLUGIN_MFA_VERSION); + + foreach (glob(dirname(__FILE__) . '/inc/*') as $filepath) { + if (preg_match("/inc.(.+)\.class.php/", $filepath, $matches)) { + $classname = 'PluginMfa' . ucfirst($matches[1]); + include_once($filepath); + if (method_exists($classname, 'uninstall')) { + $classname::install($migration); + } + } + } + $migration->executeMigration(); + + return true; +} + +function plugin_mfa_displayLogin() +{ + $url = Toolbox::getItemTypeFormURL('PluginMfaMfa'); + + $script = <<. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +if (!defined('GLPI_ROOT')) { + die("Sorry. You can't access directly to this file"); +} + +class PluginMfaConfig extends CommonDBTM +{ + static private $_instance = null; + + public function __construct() + { + global $DB; + if ($DB->tableExists($this->getTable())) { + $this->getFromDB(1); + } + } + + static function canCreate() + { + return Session::haveRight('config', UPDATE); + } + + static function canView() + { + return Session::haveRight('config', READ); + } + + static function canUpdate() + { + return Session::haveRight('config', UPDATE); + } + + static function getTypeName($nb = 0) + { + return __("MFA"); + } + + static function getInstance() + { + if (!isset(self::$_instance)) { + self::$_instance = new self(); + if (!self::$_instance->getFromDB(1)) { + self::$_instance->getEmpty(); + } + } + return self::$_instance; + } + + static function getConfig($update = false) + { + static $config = null; + if (is_null(self::$config)) { + $config = new self(); + } + if ($update) { + $config->getFromDB(1); + } + return $config; + } + + function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) + { + if ($item->getType() == 'Config') { + return self::getTypeName(); + } + return ''; + } + + static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) + { + if ($item->getType() == 'Config') { + self::showConfigForm($item); + } + return true; + } + + static function showConfigForm() + { + $config = new self(); + + $config->showFormHeader(['colspan' => 2]); + + echo ""; + echo ""; + echo ""; + Dropdown::showYesNo("local", $config->fields['local']); + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + Dropdown::showYesNo("mail", $config->fields['mail']); + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + Dropdown::showYesNo("ldap", $config->fields['ldap']); + echo ""; + echo ""; + + echo ""; + echo ""; + echo ""; + Dropdown::showYesNo("external", $config->fields['ldap']); + echo ""; + echo ""; + + $config->showFormButtons(['candel' => false]); + return false; + } + + public function needCode($authtype) { + + switch ($authtype) { + case Auth::DB_GLPI: + return $this->fields['local']; + break; + case Auth::LDAP: + return $this->fields['ldap']; + break; + case Auth::MAIL: + return $this->fields['mail']; + break; + case Auth::EXTERNAL: + return $this->fields['external']; + break; + default: + return false; + break; + } + } + + static function install(Migration $migration) + { + global $DB; + + $default_charset = DBConnection::getDefaultCharset(); + $default_collation = DBConnection::getDefaultCollation(); + $default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); + + $table = self::getTable(); + $config = new self(); + if (!$DB->tableExists($table)) { + $migration->displayMessage("Installing $table"); + $query = "CREATE TABLE IF NOT EXISTS $table ( + `id` int {$default_key_sign} NOT NULL auto_increment, + `local` tinyint NOT NULL default '1', + `mail` tinyint NOT NULL default '0', + `ldap` tinyint NOT NULL default '0', + `external` tinyint NOT NULL default '0', + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET={$default_charset} COLLATE={$default_collation} ROW_FORMAT=DYNAMIC;"; + $DB->query($query) or die($DB->error()); + + $config->add([ + 'id' => 1, + ]); + } + } +} diff --git a/inc/mfa.class.php b/inc/mfa.class.php new file mode 100644 index 0000000..44f6c4d --- /dev/null +++ b/inc/mfa.class.php @@ -0,0 +1,162 @@ +. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +if (!defined('GLPI_ROOT')) { + die("Sorry. You can't access directly to this file"); +} + +class PluginMfaMfa extends CommonDBTM +{ + + public static function getTypeName($nb = 0) + { + return __('MFA'); + } + + static function cronInfo($name) + { + switch ($name) { + case 'expiredSecurityCode': + return [ + 'description' => __('One-Time Security Code expiration', 'mfa'), + 'parameter' => __('Duration (in minutes)', 'mfa') + ]; + } + return []; + } + + static function cronExpiredSecurityCode($task) + { + global $CFG_GLPI, $DB; + + $duration = (int)$task->fields['param']; + + $query = [ + 'FROM' => self::getTable(), + 'WHERE' => [ + new QueryExpression( + sprintf( + 'ADDDATE(%s, INTERVAL %s MINUTE) <= NOW()', + $DB->quoteName('date_creation'), + $duration + ) + ), + ] + ]; + $iterator = $DB->request($query); + foreach ($iterator as $row) { + $task->addVolume(1); + $task->log( + sprintf( + __('Deleted the One-Time Security Code of the user %s'), + getUserName($row['users_id']) + ) + ); + + $mfa = new self(); + $mfa->delete(['id' => $row['id']]); + } + + return 1; + } + + public static function showCodeForm() + { + //Html::includeHeader(); + + echo "
+
+
+
+
+ +
+
+
+
+
+
+
+

" . __('One-Time Security Code', 'mfa') . "

+
+ + +
+ +
+
"; + Html::closeForm(); + echo "
+
+
+
+
"; + } + + public static function getRandomInt($length) + { + $keyspace = '0123456789'; + $str = ''; + $max = mb_strlen($keyspace, '8bit') - 1; + for ($i = 0; $i < $length; ++$i) { + $str .= $keyspace[random_int(0, $max)]; + } + if (countElementsInTable(self::getTable(), ['code' => $str]) > 0) { + $str = self::getRandomInt($length); + } + return $str; + } + + public static function install(Migration $migration) + { + global $DB; + + $default_charset = DBConnection::getDefaultCharset(); + $default_collation = DBConnection::getDefaultCollation(); + $default_key_sign = DBConnection::getDefaultPrimaryKeySignOption(); + + $table = self::getTable(); + + if (!$DB->tableExists($table)) { + $migration->displayMessage("Installing $table"); + $query = "CREATE TABLE IF NOT EXISTS $table ( + `id` int {$default_key_sign} NOT NULL auto_increment, + `users_id` int {$default_key_sign} NOT NULL DEFAULT '0', + `code` varchar(255) DEFAULT NULL, + `date_creation` timestamp NULL DEFAULT NULL, + PRIMARY KEY (`id`) + ) ENGINE=InnoDB DEFAULT CHARSET={$default_charset} COLLATE={$default_collation} ROW_FORMAT=DYNAMIC;"; + + $DB->query($query) or die($DB->error()); + } + } +} diff --git a/inc/notificationtargetmfa.class.php b/inc/notificationtargetmfa.class.php new file mode 100644 index 0000000..1e2e1ba --- /dev/null +++ b/inc/notificationtargetmfa.class.php @@ -0,0 +1,206 @@ +. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +if (!defined('GLPI_ROOT')) { + die("Sorry. You can't access directly to this file"); +} + +class PluginMfaNotificationTargetMfa extends NotificationTarget +{ + + public function getEvents() + { + return [ + 'securitycodegenerate' => __('One-Time Security Code generated'), + ]; + } + + public function addNotificationTargets($entity) + { + $this->addTarget(Notification::USER, User::getTypeName(1)); + } + + public function addSpecificTargets($data, $options) + { + //Look for all targets whose type is Notification::ITEM_USER + switch ($data['type']) { + case Notification::USER_TYPE: + switch ($data['items_id']) { + case Notification::USER: + $usertype = self::GLPI_USER; + // Send to user without any check on profile / entity + // Do not set users_id + $user = new User(); + $user->getFromDB($this->obj->fields['users_id']); + $data = [ + 'name' => $user->getName(), + 'email' => $user->getDefaultEmail(), + 'language' => $user->getField('language'), + 'usertype' => $usertype + ]; + $this->addToRecipientsList($data); + } + } + } + + public function addDataForTemplate($event, $options = []) + { + $events = $this->getEvents(); + + $this->data['##mfa.action##'] = $events[$event]; + $this->data['##mfa.code##'] = $this->obj->getField("code"); + + $this->getTags(); + foreach ($this->tag_descriptions[NotificationTarget::TAG_LANGUAGE] as $tag => $values) { + if (!isset($this->data[$tag])) { + $this->data[$tag] = $values['label']; + } + } + } + + public function getTags() + { + + // Common value tags + $tags = [ + 'mfa.code' => __('One-Time Security Code'), + 'mfa.action' => _n('Event', 'Events', 1), + ]; + + foreach ($tags as $tag => $label) { + $this->addTagToList( + [ + 'tag' => $tag, + 'label' => $label, + 'value' => true, + ] + ); + } + + $lang_tags = [ + 'mfa.information' => __('This is your security code:', 'mfa'), + 'mfa.expiration' => __('Please verify it as soon as possible; this OTP will expire quickly.', 'mfa'), + ]; + + foreach ($lang_tags as $tag => $label) { + $this->addTagToList( + [ + 'tag' => $tag, + 'label' => $label, + 'value' => false, + 'lang' => true, + ] + ); + } + + asort($this->tag_descriptions); + return $this->tag_descriptions; + } + + public static function install(Migration $migration) + { + global $DB; + + $migration->displayMessage("Migrate PluginMfaMfa notifications"); + + $template = new NotificationTemplate(); + $translation = new NotificationTemplateTranslation(); + $notification = new Notification(); + $n_n_template = new Notification_NotificationTemplate(); + $target = new NotificationTarget(); + + $itemtype = PluginMfaMfa::getType(); + $label = 'One-Time Security Code generated - MFA'; + + $templates_id = false; + + $result = $DB->request([ + 'SELECT' => 'id', + 'FROM' => NotificationTemplate::getTable(), + 'WHERE' => [ + 'itemtype' => $itemtype, + 'name' => $label + ] + ]); + + if (count($result) > 0) { + $data = $result->current(); + $templates_id = $data['id']; + } else { + $templates_id = $template->add([ + 'name' => $label, + 'itemtype' => $itemtype, + 'date_mod' => $_SESSION['glpi_currenttime'], + 'comment' => '', + 'css' => '', + ]); + } + + if ($templates_id) { + $translation_count = countElementsInTable($translation->getTable(), ['notificationtemplates_id' => $templates_id]); + if ($translation_count == 0) { + $translation->add([ + 'notificationtemplates_id' => $templates_id, + 'language' => '', + 'subject' => 'One-Time Security Code generated', + 'content_text' => '##mfa.code##', + 'content_html' => '##lang.mfa.information##
##mfa.code##
##lang.mfa.expiration##' + ]); + } + + $notications_count = countElementsInTable($notification->getTable(), ['itemtype' => $itemtype, 'event' => 'securitycodegenerate', 'name' => $label]); + + if ($notications_count == 0) { + $notification_id = $notification->add([ + 'name' => $label, + 'entities_id' => 0, + 'itemtype' => $itemtype, + 'event' => 'securitycodegenerate', + 'comment' => '', + 'is_recursive' => 1, + 'is_active' => 1, + 'date_mod' => $_SESSION['glpi_currenttime'], + ]); + + $n_n_template->add([ + 'notifications_id' => $notification_id, + 'mode' => Notification_NotificationTemplate::MODE_MAIL, + 'notificationtemplates_id' => $templates_id, + ]); + + $target->add([ + 'notifications_id' => $notification_id, + 'type' => Notification::USER_TYPE, + 'items_id' => 19 + ]); + } + } + } +} diff --git a/locales/.gitignore b/locales/.gitignore new file mode 100644 index 0000000..7948160 --- /dev/null +++ b/locales/.gitignore @@ -0,0 +1,2 @@ +localazy.keys.json +localazy.jar \ No newline at end of file diff --git a/locales/en_GB.mo b/locales/en_GB.mo new file mode 100644 index 0000000..2bd8ac4 Binary files /dev/null and b/locales/en_GB.mo differ diff --git a/locales/en_GB.po b/locales/en_GB.po new file mode 100644 index 0000000..9f022dc --- /dev/null +++ b/locales/en_GB.po @@ -0,0 +1,51 @@ +msgid "" +msgstr "" +"Project-Id-Version: MFAuth\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-26 14:09+0100\n" +"Language: en_GB\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Localazy (https://localazy.com)\n" +"Plural-Forms: nplurals=2; plural=(n==1) ? 0 : 1;\n" + +#: front/mfa.form.php:51 +msgid "Incorrect One-Time Security Code" +msgstr "Incorrect One-Time Security Code" + +#: inc/config.class.php:113 +msgid "GLPI Database Authentication" +msgstr "GLPI Database Authentication" + +#: inc/config.class.php:120 +msgid "Mail Server Authentication" +msgstr "Mail Server Authentication" + +#: inc/config.class.php:127 +msgid "LDAP Directory Authentication" +msgstr "LDAP Directory Authentication" + +#: inc/config.class.php:134 +msgid "External Authentication" +msgstr "External Authentication" + +#: inc/mfa.class.php:48 +msgid "One-Time Security Code expiration" +msgstr "One-Time Security Code expiration" + +#: inc/mfa.class.php:49 +msgid "Duration (in minutes)" +msgstr "Duration (in minutes)" + +#: inc/mfa.class.php:107 +msgid "One-Time Security Code" +msgstr "One-Time Security Code" + +#: inc/notificationtargetmfa.class.php:108 +msgid "This is your security code:" +msgstr "This is your security code:" + +#: inc/notificationtargetmfa.class.php:109 +msgid "Please verify it as soon as possible; this OTP will expire quickly." +msgstr "Please verify it as soon as possible; this OTP will expire quickly." diff --git a/locales/es_ES.mo b/locales/es_ES.mo new file mode 100644 index 0000000..9fb269d Binary files /dev/null and b/locales/es_ES.mo differ diff --git a/locales/es_ES.po b/locales/es_ES.po new file mode 100644 index 0000000..7c9ec6c --- /dev/null +++ b/locales/es_ES.po @@ -0,0 +1,51 @@ +msgid "" +msgstr "" +"Project-Id-Version: MFAuth\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-26 14:09+0100\n" +"Language: es_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Localazy (https://localazy.com)\n" +"Plural-Forms: nplurals=2; plural=(n==1) ? 0 : 1;\n" + +#: front/mfa.form.php:51 +msgid "Incorrect One-Time Security Code" +msgstr "Código de seguridad de un solo uso incorrecto" + +#: inc/config.class.php:113 +msgid "GLPI Database Authentication" +msgstr "Autenticación con la base de datos GLPI" + +#: inc/config.class.php:120 +msgid "Mail Server Authentication" +msgstr "Autenticación con servidor de correo" + +#: inc/config.class.php:127 +msgid "LDAP Directory Authentication" +msgstr "Autenticación con directorio LDAP" + +#: inc/config.class.php:134 +msgid "External Authentication" +msgstr "Autenticación externa" + +#: inc/mfa.class.php:48 +msgid "One-Time Security Code expiration" +msgstr "Caducidad del código de seguridad de un solo uso" + +#: inc/mfa.class.php:49 +msgid "Duration (in minutes)" +msgstr "Duración (en minutos)" + +#: inc/mfa.class.php:107 +msgid "One-Time Security Code" +msgstr "Código de seguridad de un solo uso" + +#: inc/notificationtargetmfa.class.php:108 +msgid "This is your security code:" +msgstr "Este es su código de seguridad:" + +#: inc/notificationtargetmfa.class.php:109 +msgid "Please verify it as soon as possible; this OTP will expire quickly." +msgstr "Por favor, verifícalo lo antes posible; esta OTP caducará rápidamente." diff --git a/locales/gl_ES.mo b/locales/gl_ES.mo new file mode 100644 index 0000000..b4f74de Binary files /dev/null and b/locales/gl_ES.mo differ diff --git a/locales/gl_ES.po b/locales/gl_ES.po new file mode 100644 index 0000000..06eb447 --- /dev/null +++ b/locales/gl_ES.po @@ -0,0 +1,51 @@ +msgid "" +msgstr "" +"Project-Id-Version: MFAuth\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-26 14:09+0100\n" +"Language: gl_ES\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"X-Generator: Localazy (https://localazy.com)\n" +"Plural-Forms: nplurals=2; plural=(n==1) ? 0 : 1;\n" + +#: front/mfa.form.php:51 +msgid "Incorrect One-Time Security Code" +msgstr "Código de seguridade único incorrecto" + +#: inc/config.class.php:113 +msgid "GLPI Database Authentication" +msgstr "Autenticación coa base de datos GLPI" + +#: inc/config.class.php:120 +msgid "Mail Server Authentication" +msgstr "Autenticación co servidor de correo" + +#: inc/config.class.php:127 +msgid "LDAP Directory Authentication" +msgstr "Autenticación co directorio LDAP" + +#: inc/config.class.php:134 +msgid "External Authentication" +msgstr "Autenticación externa" + +#: inc/mfa.class.php:48 +msgid "One-Time Security Code expiration" +msgstr "Caducidade do código de seguridade único" + +#: inc/mfa.class.php:49 +msgid "Duration (in minutes)" +msgstr "Duración (en minutos)" + +#: inc/mfa.class.php:107 +msgid "One-Time Security Code" +msgstr "Código de seguridade único" + +#: inc/notificationtargetmfa.class.php:108 +msgid "This is your security code:" +msgstr "Este é o teu código de seguranza:" + +#: inc/notificationtargetmfa.class.php:109 +msgid "Please verify it as soon as possible; this OTP will expire quickly." +msgstr "Por favor, verifícao canto antes; esta OTP caducará rapidamente." diff --git a/locales/localazy.json b/locales/localazy.json new file mode 100644 index 0000000..394d565 --- /dev/null +++ b/locales/localazy.json @@ -0,0 +1,22 @@ +{ + "upload": { + "type": "pot", + "deprecate": "project", + "files": [ + { + "pattern": "mfa.pot" + }, + { + "group": "existing", + "file": "mfa.pot", + "pattern": "./*.po", + "type": "po", + "lang": "${autodetectLang}" + } + ] + }, + "download": { + "includeSourceLang": true, + "files": "${languageCode}_${regionCode}.po" + } +} \ No newline at end of file diff --git a/locales/mfa.pot b/locales/mfa.pot new file mode 100644 index 0000000..9ca6abe --- /dev/null +++ b/locales/mfa.pot @@ -0,0 +1,58 @@ +# MFA Glpi Plugin. +# Copyright (C) 2022 TICgal +# This file is distributed under the same license as the PACKAGE package. +# TICgal, 2022 +# +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2022-12-26 14:09+0100\n" +"PO-Revision-Date: 2022-MO-DA HO:MI+ZONE\n" +"Last-Translator: FULL NAME \n" +"Language-Team: LANGUAGE \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=CHARSET\n" +"Content-Transfer-Encoding: 8bit\n" + +#: front/mfa.form.php:51 +msgid "Incorrect One-Time Security Code" +msgstr "" + +#: inc/config.class.php:113 +msgid "GLPI Database Authentication" +msgstr "" + +#: inc/config.class.php:120 +msgid "Mail Server Authentication" +msgstr "" + +#: inc/config.class.php:127 +msgid "LDAP Directory Authentication" +msgstr "" + +#: inc/config.class.php:134 +msgid "External Authentication" +msgstr "" + +#: inc/mfa.class.php:48 +msgid "One-Time Security Code expiration" +msgstr "" + +#: inc/mfa.class.php:49 +msgid "Duration (in minutes)" +msgstr "" + +#: inc/mfa.class.php:107 +msgid "One-Time Security Code" +msgstr "" + +#: inc/notificationtargetmfa.class.php:108 +msgid "This is your security code:" +msgstr "" + +#: inc/notificationtargetmfa.class.php:109 +msgid "Please verify it as soon as possible; this OTP will expire quickly." +msgstr "" diff --git a/setup.php b/setup.php new file mode 100644 index 0000000..8f215df --- /dev/null +++ b/setup.php @@ -0,0 +1,74 @@ +. + -------------------------------------------------------------------------- + @package MFA + @author the TICgal team + @copyright Copyright (c) 2022 TICgal team + @license AGPL License 3.0 or (at your option) any later version + http://www.gnu.org/licenses/agpl-3.0-standalone.html + @link https://www.tic.gal + @since 2022 + ---------------------------------------------------------------------- +*/ + +define('PLUGIN_MFA_VERSION', '1.0.1'); +define('PLUGIN_MFA_MIN_GLPI', '10.0.0'); +define('PLUGIN_MFA_MAX_GLPI', '10.1.99'); + +function plugin_version_mfa() +{ + return [ + 'name' => 'MFA', + 'version' => PLUGIN_MFA_VERSION, + 'author' => 'TICgal', + 'homepage' => 'https://tic.gal', + 'license' => 'GPLv3+', + 'requirements' => [ + 'glpi' => [ + 'min' => PLUGIN_MFA_MIN_GLPI, + 'max' => PLUGIN_MFA_MAX_GLPI + ] + ] + ]; +} + +function plugin_init_mfa() +{ + global $PLUGIN_HOOKS; + + $PLUGIN_HOOKS['csrf_compliant']['mfa'] = true; + + $plugin = new Plugin(); + if ($plugin->isActivated('mfa')) { + Plugin::registerClass('PluginMfaConfig', ['addtabon' => 'Config']); + $PLUGIN_HOOKS['config_page']['mfa'] = 'front/config.form.php'; + + Plugin::registerClass('PluginMfaMfa', [ + 'notificationtemplates_types' => true, + ]); + $PLUGIN_HOOKS['display_login']['mfa'] = 'plugin_mfa_displayLogin'; + + Crontask::Register('PluginMfaMfa', 'expiredSecurityCode', HOUR_TIMESTAMP, [ + 'param' => 5, + 'state' => 1, + 'mode' => CronTask::MODE_EXTERNAL + ]); + } +} diff --git a/tools/generate_locales.sh b/tools/generate_locales.sh new file mode 100644 index 0000000..9a85f4f --- /dev/null +++ b/tools/generate_locales.sh @@ -0,0 +1,22 @@ +#!/bin/bash + +CUR_PATH="`dirname \"$0\"`" + +cd "$CUR_PATH/.." + +xgettext *.php */*.php -o locales/mfa.pot -L PHP --add-comments=TRANS --from-code=UTF-8 --force-po -k --keyword=__:1,2t --keyword=_x:1,2,3t --keyword=__s:1,2t --keyword=_sx:1,2,3t --keyword=_n:1,2,3,4t --keyword=_sn:1,2t --keyword=_nx:1,2,3t --copyright-holder "TICgal" + +cd locales + +sed -i "s/SOME DESCRIPTIVE TITLE/MFA Glpi Plugin/" mfa.pot +sed -i "s/FIRST AUTHOR , YEAR./TICgal, $(date +%Y)/" mfa.pot +sed -i "s/YEAR/$(date +%Y)/" mfa.pot + +localazy upload +localazy download + +for a in $(ls *.po); do + msgmerge -U $a mfa.pot + msgfmt $a -o "${a%.*}.mo" +done +rm -f *.po~ diff --git a/tools/make_release.sh b/tools/make_release.sh new file mode 100644 index 0000000..60f7b4f --- /dev/null +++ b/tools/make_release.sh @@ -0,0 +1,92 @@ +#!/bin/bash + +PLUGINNAME="mfa" + +if [ ! "$#" -eq 2 ] +then + echo "Usage $0 fi_git_dir release" + exit +fi + +read -p "Are translations up to date? [Y/n] " -n 1 -r +echo # (optional) move to a new line +if [[ ! $REPLY =~ ^[Yy]$ ]] + then + [[ "$0" = "$BASH_SOURCE" ]] && exit 1 || return 1 # handle exits from shell or function but don't exit interactive shell +fi + +INIT_DIR=$1 +RELEASE=$2 + +# remove old tmp files +if [ ! -e /tmp/$PLUGINNAME ] +then + echo "Deleting temp directory" + rm -rf /tmp/$PLUGINNAME +fi + +# test plugin_cvs_dir +if [ ! -e $INIT_DIR ] +then + echo "$1 does not exist" + exit +fi + +INIT_PWD=$PWD; + +if [ -e /tmp/$PLUGINNAME ] +then + echo "Delete existing temp directory" + rm -rf /tmp/$PLUGINNAME +fi + +echo "Copy to /tmp directory" +git checkout-index -a -f --prefix=/tmp/$PLUGINNAME/ + +if [ -e vendor ] +then + cp -R vendor/ /tmp/$PLUGINNAME/ +fi + +echo "Move to this directory" +cd /tmp/$PLUGINNAME + +echo "Check version" +if grep --quiet $RELEASE setup.php; then + echo "$RELEASE found in setup.php, OK." +else + echo "$RELEASE has not been found in setup.php. Exiting." + exit 1 +fi + +echo "Compile locale files" +./tools/generate_locales.sh + +echo "Delete various scripts and directories" +rm -rf RoboFile.php +rm -rf tools +rm -rf phpunit +rm -rf tests +rm -rf .gitignore +rm -rf .travis.yml +rm -rf .coveralls.yml +rm -rf phpunit.xml.dist +rm -rf composer.lock +rm -rf .composer.hash +rm -rf ISSUE_TEMPLATE.md +rm -rf PULL_REQUEST_TEMPLATE.md +rm -rf .tx +rm -rf $PLUGINNAME.xml +rm -rf screenshots +rm -rf locales/localazy* + +echo "Creating tarball" +cd .. +tar czf "$PLUGINNAME-$RELEASE.tar.tgz" $PLUGINNAME + +cd $INIT_PWD; + +echo "Deleting temp directory" +rm -rf /tmp/$PLUGINNAME + +echo "The Tarball is in the /tmp directory" \ No newline at end of file