From c9c2dce7722b4cffaea55654603c99644c9e8182 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Tue, 7 Jan 2025 07:51:02 +0100 Subject: [PATCH 01/11] #390 remove default password --- lam-packaging/debian/README.Debian | 1 - lam/README | 3 --- lam/config/addressbook.sample.conf | 1 - lam/config/config.cfg.sample | 1 - lam/config/samba3.sample.conf | 1 - lam/config/unix.sample.conf | 1 - lam/config/windows_samba4.sample.conf | 1 - lam/docs/manual-sources/chapter-configuration.xml | 5 ----- lam/docs/manual-sources/overview.xml | 11 ----------- 9 files changed, 25 deletions(-) diff --git a/lam-packaging/debian/README.Debian b/lam-packaging/debian/README.Debian index dbc34a966..34698b55a 100644 --- a/lam-packaging/debian/README.Debian +++ b/lam-packaging/debian/README.Debian @@ -14,7 +14,6 @@ Configuration: All settings can be edited via the webfrontend. Please point your browser to the LAM start page and then select "LAM configuration". - The default password for the configuration is "lam". Lamdaemon: diff --git a/lam/README b/lam/README index b9aab3c01..a7bd58eb9 100644 --- a/lam/README +++ b/lam/README @@ -15,9 +15,6 @@ LAM - Readme Installation and documentation: Please see the LAM manual in docs/manual/index.html. - Default password: - The default password to edit the configuration options is "lam". - Download: You can get the newest version at https://www.ldap-account-manager.org/. diff --git a/lam/config/addressbook.sample.conf b/lam/config/addressbook.sample.conf index ba6fa60d4..3b46d4538 100644 --- a/lam/config/addressbook.sample.conf +++ b/lam/config/addressbook.sample.conf @@ -3,7 +3,6 @@ "useTLS": "yes", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$ZJcXwaxHP0GQH0Rd$Ggkn8Wz\/8ntCM9v0TywomjkgSvV.3BoayFwnc9QP3MV.b7HWaqLOA8urP2e7HyEmU\/JmC8xR7jTqrXCHC4kFr. WkpjWHdheEhQMEdRSDBSZA==", "Admins": "cn=Manager,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/config/config.cfg.sample b/lam/config/config.cfg.sample index 046734c5e..f1ed0d05d 100644 --- a/lam/config/config.cfg.sample +++ b/lam/config/config.cfg.sample @@ -1,5 +1,4 @@ { - "password": "{CRYPT-SHA512}$6$WheNHdlVwDoL4s.x$DrZ10TpIGQa5wd0jbvtm8eaTleJCf1nec3ihOaNwMdPUKVFCphXwtnTSmFFXjhGa45RlrSEWhDVyjLCMiV\/.c. V2hlTkhkbFZ3RG9MNHMueA==", "default": "lam", "sessionTimeout": "30", "hideLoginErrorDetails": "false", diff --git a/lam/config/samba3.sample.conf b/lam/config/samba3.sample.conf index 66c7864f4..4bfddb42e 100644 --- a/lam/config/samba3.sample.conf +++ b/lam/config/samba3.sample.conf @@ -3,7 +3,6 @@ "useTLS": "yes", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$MUWJEkvtUY7G5sFA$QS6voQCksH9gNbbbQpjDKt65iez9bgKQI2x60DAffCK5.LO\/\/QfYTetQ6V2PlUR32CTkuhlSXSGXnH9scD\/zb0 TVVXSkVrdnRVWTdHNXNGQQ==", "Admins": "cn=Manager,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/config/unix.sample.conf b/lam/config/unix.sample.conf index 53aedf331..865c24cd5 100644 --- a/lam/config/unix.sample.conf +++ b/lam/config/unix.sample.conf @@ -3,7 +3,6 @@ "useTLS": "no", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$zvb8WVEHSAKEGtGO$573kA9Us8LtGLLm5Gu87P\/vIiF\/2Ol\/DauzPmUpvC4eCL\/t0WWiwBaY19Rx5G3wzbeZWWlE1kp2fikrpZTZ51\/ enZiOFdWRUhTQUtFR3RHTw==", "Admins": "cn=Manager,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/config/windows_samba4.sample.conf b/lam/config/windows_samba4.sample.conf index e521c78a7..f2ab63f47 100644 --- a/lam/config/windows_samba4.sample.conf +++ b/lam/config/windows_samba4.sample.conf @@ -3,7 +3,6 @@ "useTLS": "no", "followReferrals": "false", "pagedResults": "false", - "Passwd": "{CRYPT-SHA512}$6$9IWWua4lbp7uiLCC$AHPgST1YAm3yUAWKGeNZ5f9GCo1wBGyVo3MGvAt6.UOtQ9dYxs4WeQ4mlzjR30rD6cRayMNRBWqYFuBLvzn9T0 OUlXV3VhNGxicDd1aUxDQw==", "Admins": "cn=Administrator,cn=users,dc=my-domain,dc=com", "defaultLanguage": "en_GB.utf8", "scriptPath": "", diff --git a/lam/docs/manual-sources/chapter-configuration.xml b/lam/docs/manual-sources/chapter-configuration.xml index 4cdf0a059..f82352a9f 100644 --- a/lam/docs/manual-sources/chapter-configuration.xml +++ b/lam/docs/manual-sources/chapter-configuration.xml @@ -44,11 +44,6 @@
General settings - After selecting "Edit general settings" you will need to enter the - master configuration password. - The default password for new installations is "lam". Now you can edit the - general settings. -
Configuration Database diff --git a/lam/docs/manual-sources/overview.xml b/lam/docs/manual-sources/overview.xml index c12fe9bf1..f9c57580f 100644 --- a/lam/docs/manual-sources/overview.xml +++ b/lam/docs/manual-sources/overview.xml @@ -87,26 +87,15 @@ Edge (max. 2 years old) - - - Opera (max. 2 years old) - - The default password to edit the configuration options is - "lam". - License: LAM is published under the GNU General Public License. The complete list of licenses can be found in the copyright file. - Default password: - - The default password for the LAM configuration is "lam". - Have fun! The LAM development team From 5176eacdcb9ed8c8d6c8cc05bd753b65bfbae9f1 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Thu, 9 Jan 2025 19:49:29 +0100 Subject: [PATCH 02/11] #395 i18n fix --- lam/locale/es_ES/LC_MESSAGES/messages.mo | Bin 371061 -> 371077 bytes lam/locale/es_ES/LC_MESSAGES/messages.po | 9 +++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lam/locale/es_ES/LC_MESSAGES/messages.mo b/lam/locale/es_ES/LC_MESSAGES/messages.mo index 4a2d51003cbc58e267dbc2ce2f2bcb913ffb4bb7..9c5853e96bb5ca14a06a7138a89ec6c63c5b9ac6 100644 GIT binary patch delta 27752 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�m!dj&fLf9y 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 delta 27748 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 diff --git a/lam/locale/es_ES/LC_MESSAGES/messages.po b/lam/locale/es_ES/LC_MESSAGES/messages.po index 68ac6c6ee..a0ef96e4d 100644 --- a/lam/locale/es_ES/LC_MESSAGES/messages.po +++ b/lam/locale/es_ES/LC_MESSAGES/messages.po @@ -5,12 +5,13 @@ # Julio C. Ortega, 2022-2024 # Julio C. Ortega, 2015-2021 # Leandro Lattanzio, 2023 +# Roland Gruber , 2025 msgid "" msgstr "" "Project-Id-Version: LDAP Account Manager\n" "Report-Msgid-Bugs-To: post@rolandgruber.de\n" "PO-Revision-Date: 2011-09-29 18:53+0000\n" -"Last-Translator: Julio C. Ortega, 2022-2024\n" +"Last-Translator: Roland Gruber , 2025\n" "Language-Team: Spanish (Spain) (http://app.transifex.com/gruberroland/lam/language/es_ES/)\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -8775,11 +8776,11 @@ msgstr "Móvil" #: ../lib/modules/inetOrgPerson.inc:3760 ../lib/modules/inetOrgPerson.inc:3802 #: ../lib/modules/inetOrgPerson.inc:4147 msgid "Mobile number" -msgstr "Numero de ḿmóbil" +msgstr "Número de móvil" #: ../lib/modules/inetOrgPerson.inc:2751 msgid "Mobile telephone number" -msgstr "Número de teléfono movil" +msgstr "Número de teléfono móvil" #: ../lib/modules/qmailGroup.inc:183 ../lib/modules/qmailGroup.inc:187 #: ../lib/modules/qmailGroup.inc:316 ../lib/modules/qmailGroup.inc:376 @@ -16357,7 +16358,7 @@ msgstr "Número de Fax del usuario." #: ../lib/modules/windowsLDSUser.inc:312 ../lib/modules/inetOrgPerson.inc:648 #: ../lib/modules/inetOrgPerson.inc:652 ../lib/modules/windowsUser.inc:423 msgid "The user's mobile number." -msgstr "Numero de móvil del usuario." +msgstr "Número de móvil del usuario." #: ../lib/modules/windowsLDSUser.inc:284 ../lib/modules/windowsLDSUser.inc:288 #: ../lib/modules/inetOrgPerson.inc:780 ../lib/modules/inetOrgPerson.inc:784 From ac447b3840c27fe5fde8a8b8e9d6285d3b57b5dc Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 10 Jan 2025 07:57:36 +0100 Subject: [PATCH 03/11] #390 remove default password --- lam/lib/config.inc | 35 +++++- lam/templates/config/mainmanage.php | 17 ++- lam/templates/config/profmanage.php | 13 ++- lam/templates/login.php | 33 +++--- lam/templates/setInitialPassword.php | 162 +++++++++++++++++++++++++++ lam/tests/lib/LAMCfgMainTest.php | 4 +- 6 files changed, 235 insertions(+), 29 deletions(-) create mode 100644 lam/templates/setInitialPassword.php diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 90ac5bdf5..296b599d4 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -12,7 +12,7 @@ use function LAM\TYPES\getScopeFromTypeId; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -50,6 +50,20 @@ include_once __DIR__ . "/types.inc"; /** 2-factor */ include_once __DIR__ . '/2factor.inc'; +/** + * Checks if the configuration password is secure. + * + * @param string $password password + * @return bool is secure + */ +function isValidConfigurationPassword(string $password): bool { + return preg_match('/[a-z]/', $password) + && preg_match('/[A-Z]/', $password) + && preg_match('/[0-9]/', $password) + && preg_match('/[^a-zA-Z0-9]/', $password) + && (strlen($password) >= 8); +} + /** * Sets the environment variables for custom SSL CA certificates. */ @@ -3242,8 +3256,9 @@ class LAMCfgMain { /** * Saves the configuration to the persistence layer. + * @throws LAMException error saving config */ - public function save() { + public function save(): void { if ($this->configDatabaseType === self::DATABASE_MYSQL) { $this->saveLocal(true); $this->saveDb(); @@ -3261,7 +3276,7 @@ class LAMCfgMain { @chmod($sslPath, 0600); } else { - StatusMessage("ERROR", _("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); + throw new LAMException(_("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); } } // delete SSL certificate @@ -3269,7 +3284,7 @@ class LAMCfgMain { $sslPath = $this->getInternalSSLCaCertFileName(); $result = @unlink($sslPath); if (!$result) { - StatusMessage("ERROR", _("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); + throw new LAMException(_("Cannot write certificate file. Please check the permissions of config/serverCerts.pem.")); } } } @@ -3296,6 +3311,7 @@ class LAMCfgMain { * Saves preferences to config file config.cfg * * @param bool $persistenceOnly store only persistence related data + * @throws LAMException error saving config */ public function saveLocal(bool $persistenceOnly): void { $data = $persistenceOnly ? $this->exportPersistenceData() : $this->exportData(); @@ -3308,10 +3324,19 @@ class LAMCfgMain { chmod($this->conffile, 0600); } else { - StatusMessage("ERROR", "", _("Cannot open config file!") . " (" . $this->conffile . ")"); + throw new LAMException(_("Cannot open config file!") . " (" . $this->conffile . ")"); } } + /** + * Returns if the main config has a password set. + * + * @return bool password is set + */ + public function hasPasswordSet(): bool { + return ($this->password != null) && ($this->password !== ''); + } + /** * Sets a new config password. * diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index 5c13990d6..0c6205cc9 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -33,7 +33,7 @@ use PDO; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -388,12 +388,17 @@ if (isset($_POST['submitFormData'])) { $cfg->setModuleSettings($moduleSettings); // save settings if (isset($_POST['submit'])) { - $cfg->save(); if (empty($errors)) { - $scriptTag = new htmlJavaScript('window.lam.dialog.showSuccessMessageAndRedirect("' . _("Your settings were successfully saved.") . '", "", "' . _('Ok') . '", "../login.php")'); - parseHtml(null, $scriptTag, [], false, null); - echo ''; - exit(); + try { + $cfg->save(); + $scriptTag = new htmlJavaScript('window.lam.dialog.showSuccessMessageAndRedirect("' . _("Your settings were successfully saved.") . '", "", "' . _('Ok') . '", "../login.php")'); + parseHtml(null, $scriptTag, [], false, null); + echo ''; + exit(); + } + catch (LAMException $e) { + $errors[] = $e->getTitle(); + } } } } diff --git a/lam/templates/config/profmanage.php b/lam/templates/config/profmanage.php index 8f1699474..40bccaf4e 100644 --- a/lam/templates/config/profmanage.php +++ b/lam/templates/config/profmanage.php @@ -19,7 +19,7 @@ use ServerProfilePersistenceManager; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2024 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -155,9 +155,14 @@ if (isset($_POST['action'])) { if (preg_match("/^[a-z0-9_-]+$/i", (string) $_POST['defaultfilename'])) { $configMain = new LAMCfgMain(); $configMain->default = $_POST['defaultfilename']; - $configMain->save(); - $configMain = null; - $msg = _("New default profile set successfully."); + try { + $configMain->save(); + $configMain = null; + $msg = _("New default profile set successfully."); + } + catch (LAMException $e) { + $error = $e->getTitle(); + } } else { $error = _("Profile name is invalid!"); diff --git a/lam/templates/login.php b/lam/templates/login.php index 61f47e3a5..9748bfcc5 100644 --- a/lam/templates/login.php +++ b/lam/templates/login.php @@ -26,7 +26,7 @@ use ServerProfilePersistenceManager; This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) Copyright (C) 2003 - 2006 Michael Duergner - 2005 - 2024 Roland Gruber + 2005 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -60,10 +60,19 @@ include __DIR__ . '/../lib/checkEnvironment.inc'; /** security functions */ include_once(__DIR__ . "/../lib/security.inc"); -/** self service functions */ +/** self-service functions */ include_once(__DIR__ . "/../lib/selfService.inc"); /** access to configuration options */ include_once(__DIR__ . "/../lib/config.inc"); + +$cfgMain = new LAMCfgMain(); + +// check if main config password is set +if (!$cfgMain->hasPasswordSet()) { + metaRefresh('setInitialPassword.php'); + die(); +} + $licenseValidator = null; if (isLAMProVersion()) { include_once(__DIR__ . "/../lib/env.inc"); @@ -110,12 +119,10 @@ if (isset($_POST['language'])) { setcookie('lam_last_language', htmlspecialchars((string) $_POST['language']), $cookieOptions); } -// init some session variables -$default_Config = new LAMCfgMain(); -$_SESSION["cfgMain"] = $default_Config; +$_SESSION["cfgMain"] = $cfgMain; setSSLCaCert(); -$default_Profile = $default_Config->default; +$default_Profile = $cfgMain->default; if (isset($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $profiles)) { $default_Profile = $_COOKIE["lam_default_profile"]; } @@ -142,7 +149,7 @@ catch (LAMException $e) { $error_message = $e->getTitle(); } -if (!isset($default_Config->default) || !in_array($default_Config->default, $profiles)) { +if (!isset($cfgMain->default) || !in_array($cfgMain->default, $profiles)) { $error_message = _('No default profile set. Please set it in the server profile configuration.'); } @@ -549,7 +556,7 @@ if (isset($_POST['checklogin'])) { cleanLDAPResult($searchInfo); if (empty($searchInfo)) { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -560,7 +567,7 @@ if (isset($_POST['checklogin'])) { } elseif (count($searchInfo) > 1) { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -575,7 +582,7 @@ if (isset($_POST['checklogin'])) { } else { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -590,7 +597,7 @@ if (isset($_POST['checklogin'])) { } else { $searchSuccess = false; - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $searchError = _('Wrong password/user name combination. Please try again.'); } else { @@ -641,13 +648,13 @@ if (isset($_POST['checklogin'])) { header("HTTP/1.1 403 Forbidden"); $extraMessage = null; if (($searchLDAP !== null) && ($e->getLdapErrorCode() == 49)) { - if (!$default_Config->isHideLoginErrorDetails()) { + if (!$cfgMain->isHideLoginErrorDetails()) { $extraMessage = getExtraInvalidCredentialsMessage($searchLDAP->server(), $username); } $searchLDAP->close(); } $message = $e->getMessage(); - if ($default_Config->isHideLoginErrorDetails()) { + if ($cfgMain->isHideLoginErrorDetails()) { $message = null; } display_LoginPage($licenseValidator, $e->getTitle(), $message, $extraMessage); diff --git a/lam/templates/setInitialPassword.php b/lam/templates/setInitialPassword.php new file mode 100644 index 000000000..80654310c --- /dev/null +++ b/lam/templates/setInitialPassword.php @@ -0,0 +1,162 @@ +hasPasswordSet()) { + logNewMessage(LOG_ERR, 'Invalid attempt to set initial config password'); + die(); +} + +setlanguage(); + +if (!empty($_POST)) { + validateSecurityToken(); +} + +$message = null; + +// check if user already pressed button +if (isset($_POST['changePassword'])) { + // check new password + $password1 = $_POST['password1']; + $password2 = $_POST['password2']; + if ($password1 == '') { + $message = new htmlStatusMessage('ERROR', _('No password was entered!')); + printContent($message); + exit(); + } + // check if passwords match + if ($password1 != $password2) { + $message = new htmlStatusMessage('ERROR', _('Passwords are different!')); + printContent($message); + exit(); + } + // check password strength + $pwdPolicyResult = isValidConfigurationPassword($password1); + if (!$pwdPolicyResult) { + $message = new htmlStatusMessage('ERROR', _('Please enter at least 8 characters including small and big letters, a number and a symbol.')); + printContent($message); + exit(); + } + // set new password + $cfgMain->setPassword($password1); + try { + $cfgMain->save(); + metaRefresh('config/mainlogin.php'); + exit(); + } + catch (LAMException $e) { + $message = new htmlStatusMessage('ERROR', $e->getTitle(), $e->getMessage()); + } +} + +printContent($message); + +/** + * Displays the content area + * + * @param htmlStatusMessage|null $message status message + * @param bool $showPasswordInputs show password input fields + */ +function printContent(htmlStatusMessage $message = null, bool $showPasswordInputs = true): void { + echo ' + + + + + + + '; + printHeaderContents('LDAP Account Manager', '..'); + echo ' + '; + printJsIncludes('..'); + + echo '
'; + echo "
\n"; + addSecurityTokenToSession(); + $container = new htmlResponsiveRow(); + if ($message !== null) { + $container->addVerticalSpacer('1rem'); + $container->add($message); + } + $container->addVerticalSpacer('2rem'); + if ($showPasswordInputs) { + $container->add(new htmlTitle(_('Initial configuration'))); + $container->addVerticalSpacer('3rem'); + $container->add(new htmlOutputText(_("Please enter your new LAM main configuration password.")), 12, 12, 12, 'text-center'); + $container->addVerticalSpacer('2rem'); + $pwdInput1 = new htmlResponsiveInputField(_('New password'), 'password1', ''); + $pwdInput1->setIsPassword(true, true, true); + $container->add($pwdInput1); + $pwdInput2 = new htmlResponsiveInputField(_('Repeat password'), 'password2', ''); + $pwdInput2->setIsPassword(true); + $pwdInput2->setSameValueFieldID('password1'); + $container->add($pwdInput2); + $container->addVerticalSpacer('1rem'); + $container->add(new htmlButton('changePassword', _("Submit")), 12, 12, 12, 'text-center'); + addSecurityTokenToMetaHTML($container); + $container->add(new htmlJavaScript('checkFieldsHaveSameValues("password1", "password2");')); + } + + parseHtml(null, $container, [], false, 'user'); + + echo '

+
+

+ + '; +} diff --git a/lam/tests/lib/LAMCfgMainTest.php b/lam/tests/lib/LAMCfgMainTest.php index a25832a2a..dc2d4b0c3 100644 --- a/lam/tests/lib/LAMCfgMainTest.php +++ b/lam/tests/lib/LAMCfgMainTest.php @@ -3,7 +3,7 @@ use PHPUnit\Framework\TestCase; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2020 - 2023 Roland Gruber + Copyright (C) 2020 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -56,6 +56,7 @@ class LAMCfgMainTest extends TestCase { /** * Mail related settings + * @throws LAMException error saving config */ public function testMail() { $this->assertEquals(LAMCfgMain::MAIL_ATTRIBUTE_DEFAULT, $this->conf->getMailAttribute()); @@ -81,6 +82,7 @@ class LAMCfgMainTest extends TestCase { /** * License related settings. + * @throws LAMException error saving config */ public function testLicense() { $timestamp = '12345'; From 875f8241f9455be76ead10931569e6a794636b53 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Fri, 10 Jan 2025 20:50:37 +0100 Subject: [PATCH 04/11] #390 remove default password --- lam/HISTORY | 1 + .../appendix-troubleshooting.xml | 18 +++++++----------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/lam/HISTORY b/lam/HISTORY index b2d6d5790..6f61b00e2 100644 --- a/lam/HISTORY +++ b/lam/HISTORY @@ -1,5 +1,6 @@ March 2025 9.1 - Usability improvements (348, 360) + - Security: LAM no longer ships with any default passwords, main configuration password is requested on login if not yet set (#390) - Fixed bugs: -> Ambiguous tooltip on profile editor for Shadow users (#394) -> Self service photo file enhancements (#396) diff --git a/lam/docs/manual-sources/appendix-troubleshooting.xml b/lam/docs/manual-sources/appendix-troubleshooting.xml index 97e6763b5..eaf6cfa62 100644 --- a/lam/docs/manual-sources/appendix-troubleshooting.xml +++ b/lam/docs/manual-sources/appendix-troubleshooting.xml @@ -28,27 +28,23 @@ Locate config.cfg: On DEB/RPM installations it is in - /usr/share/ldap-account-manager/config and for tar.bz2 in config + /usr/share/ldap-account-manager/config and + for tar.bz2 in config folder. - Locate the "password" entry in the file + Locate the "password" line in the file - Replace the password hash after "password: " with your new - clear-text password (e.g. "secret") + Remove the password line in the configuration file - After the change the line should look like this: - - password: secret - - You can now login using your new password. Set the password once - again via GUI in main configuration settings. This will then put again - a hash value in the config.cfg file. + When you open LAM's start page you will now be asked to set a + new password.
From ecba26710194bca3d1368940ab814495af9b103c Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Sun, 12 Jan 2025 21:00:57 +0100 Subject: [PATCH 05/11] #390 password policy for configuration --- lam/lib/config.inc | 3 +- lam/templates/config/mainmanage.php | 448 ++++++++++++++------------- lam/templates/setInitialPassword.php | 2 +- 3 files changed, 233 insertions(+), 220 deletions(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index 296b599d4..be516dc30 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -57,8 +57,7 @@ include_once __DIR__ . '/2factor.inc'; * @return bool is secure */ function isValidConfigurationPassword(string $password): bool { - return preg_match('/[a-z]/', $password) - && preg_match('/[A-Z]/', $password) + return preg_match('/[a-zA-Z]/', $password) && preg_match('/[0-9]/', $password) && preg_match('/[^a-zA-Z0-9]/', $password) && (strlen($password) >= 8); diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index 0c6205cc9..28b52468c 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -4,28 +4,28 @@ namespace LAM\CONFIG; use htmlJavaScript; use htmlResponsiveTable; use LAM\LOGIN\WEBAUTHN\WebauthnManager; -use \LAMCfgMain; -use \htmlTable; -use \htmlTitle; -use \htmlStatusMessage; -use \htmlSubTitle; -use \htmlSpacer; -use \htmlOutputText; -use \htmlLink; -use \htmlGroup; -use \htmlButton; -use \htmlHelpLink; -use \htmlInputField; -use \htmlInputFileUpload; -use \DateTime; -use \DateTimeZone; -use \htmlResponsiveRow; -use \htmlResponsiveInputTextarea; -use \htmlResponsiveSelect; -use \htmlResponsiveInputCheckbox; -use \htmlResponsiveInputField; -use \htmlDiv; -use \htmlHiddenInput; +use LAMCfgMain; +use htmlTable; +use htmlTitle; +use htmlStatusMessage; +use htmlSubTitle; +use htmlSpacer; +use htmlOutputText; +use htmlLink; +use htmlGroup; +use htmlButton; +use htmlHelpLink; +use htmlInputField; +use htmlInputFileUpload; +use DateTime; +use DateTimeZone; +use htmlResponsiveRow; +use htmlResponsiveInputTextarea; +use htmlResponsiveSelect; +use htmlResponsiveInputCheckbox; +use htmlResponsiveInputField; +use htmlDiv; +use htmlHiddenInput; use LAMException; use LamTemporaryFilesManager; use PDO; @@ -114,7 +114,8 @@ printHeaderContents(_("Edit general settings"), '../..'); - + menu   @@ -134,37 +135,41 @@ $errors = []; $messages = []; // check if submit button was pressed if (isset($_POST['submitFormData'])) { - if (extension_loaded('PDO')) { - // set database - $cfg->configDatabaseType = $_POST['configDatabaseType']; - $cfg->configDatabaseServer = $_POST['configDatabaseServer']; - $cfg->configDatabasePort = $_POST['configDatabasePort']; - $cfg->configDatabaseName = $_POST['configDatabaseName']; - $cfg->configDatabaseUser = $_POST['configDatabaseUser']; - $cfg->configDatabasePassword = $_POST['configDatabasePassword']; - if ($cfg->configDatabaseType === LAMCfgMain::DATABASE_MYSQL) { - if (empty($cfg->configDatabaseServer) || !get_preg($cfg->configDatabaseServer, 'hostname')) { - $errors[] = _('Please enter a valid database host name.'); - } - if (empty($cfg->configDatabaseName)) { - $errors[] = _('Please enter a valid database name.'); - } - if (empty($cfg->configDatabaseUser)) { - $errors[] = _('Please enter a valid database user.'); - } - if (empty($cfg->configDatabasePassword)) { - $errors[] = _('Please enter a valid database password.'); - } - } - } + if (extension_loaded('PDO')) { + // set database + $cfg->configDatabaseType = $_POST['configDatabaseType']; + $cfg->configDatabaseServer = $_POST['configDatabaseServer']; + $cfg->configDatabasePort = $_POST['configDatabasePort']; + $cfg->configDatabaseName = $_POST['configDatabaseName']; + $cfg->configDatabaseUser = $_POST['configDatabaseUser']; + $cfg->configDatabasePassword = $_POST['configDatabasePassword']; + if ($cfg->configDatabaseType === LAMCfgMain::DATABASE_MYSQL) { + if (empty($cfg->configDatabaseServer) || !get_preg($cfg->configDatabaseServer, 'hostname')) { + $errors[] = _('Please enter a valid database host name.'); + } + if (empty($cfg->configDatabaseName)) { + $errors[] = _('Please enter a valid database name.'); + } + if (empty($cfg->configDatabaseUser)) { + $errors[] = _('Please enter a valid database user.'); + } + if (empty($cfg->configDatabasePassword)) { + $errors[] = _('Please enter a valid database password.'); + } + } + } // set master password - if (isset($_POST['masterpassword']) && ($_POST['masterpassword'] != "")) { - if ($_POST['masterpassword'] && $_POST['masterpassword2'] && ($_POST['masterpassword'] == $_POST['masterpassword2'])) { + if (!empty($_POST['masterpassword'])) { + if (($_POST['masterpassword'] !== $_POST['masterpassword2'])) { + $errors[] = _("Master passwords are different."); + } + elseif (!isValidConfigurationPassword($_POST['masterpassword'])) { + $errors[] = _('Please enter at least 8 characters including letters, a number and a symbol.'); + } + else { $cfg->setPassword($_POST['masterpassword']); $msg = _("New master password set successfully."); unset($_SESSION["mainconf_password"]); - } else { - $errors[] = _("Master passwords are different or empty!"); } } // set license @@ -176,19 +181,19 @@ if (isset($_POST['submitFormData'])) { $cfg->licenseEmailFrom = $_POST['licenseEmailFrom']; $cfg->licenseEmailTo = $_POST['licenseEmailTo']; if ((($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_EMAIL) || ($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_ALL)) - && !get_preg($cfg->licenseEmailFrom, 'email')) { - $errors[] = _('Licence') . ': ' . _('From address') . ' - ' . _('Please enter a valid email address!'); - } + && !get_preg($cfg->licenseEmailFrom, 'email')) { + $errors[] = _('Licence') . ': ' . _('From address') . ' - ' . _('Please enter a valid email address!'); + } if (($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_EMAIL) || ($cfg->licenseWarningType === LAMCfgMain::LICENSE_WARNING_ALL)) { - $toEmails = preg_split('/;[ ]*/', $cfg->licenseEmailTo); - if ($toEmails !== false) { + $toEmails = preg_split('/;[ ]*/', $cfg->licenseEmailTo); + if ($toEmails !== false) { foreach ($toEmails as $toEmail) { if (!get_preg($toEmail, 'email')) { $errors[] = _('Licence') . ': ' . _('To address') . ' - ' . _('Please enter a valid email address!'); break; } } - } + } } } // set session timeout @@ -213,11 +218,12 @@ if (isset($_POST['submitFormData'])) { } } $allowedHosts = implode(",", $allowedHostsList); - } else { + } + else { $allowedHosts = ""; } $cfg->allowedHosts = $allowedHosts; - // set allowed hosts for self service + // set allowed hosts for self-service if (isLAMProVersion()) { if (isset($_POST['allowedHostsSelfService'])) { $allowedHostsSelfService = $_POST['allowedHostsSelfService']; @@ -237,7 +243,8 @@ if (isset($_POST['submitFormData'])) { } $allowedHostsSelfServiceList = array_unique($allowedHostsSelfServiceList); $allowedHostsSelfService = implode(",", $allowedHostsSelfServiceList); - } else { + } + else { $allowedHostsSelfService = ""; } $cfg->allowedHostsSelfService = $allowedHostsSelfService; @@ -247,34 +254,37 @@ if (isset($_POST['submitFormData'])) { // set log destination if ($_POST['logDestination'] == "none") { $cfg->logDestination = "NONE"; - } elseif ($_POST['logDestination'] == "syslog") { + } + elseif ($_POST['logDestination'] == "syslog") { $cfg->logDestination = "SYSLOG"; - } elseif ($_POST['logDestination'] == "remote") { + } + elseif ($_POST['logDestination'] == "remote") { $cfg->logDestination = "REMOTE:" . $_POST['logRemote']; $remoteParts = explode(':', $_POST['logRemote']); if ((count($remoteParts) !== 2) || !get_preg($remoteParts[0], 'DNSname') || !get_preg($remoteParts[1], 'digit')) { $errors[] = _("Please enter a valid remote server in format \"server:port\"."); } - } else { - $isValidLogFile = isset($_POST['logFile']) && LAMCfgMain::isValidLogFilename($_POST['logFile']); + } + else { + $isValidLogFile = isset($_POST['logFile']) && LAMCfgMain::isValidLogFilename($_POST['logFile']); $blockedPrefixes = ['/usr', '/etc', '/dev', '/boot', '/lib', '/proc', '/root', '/run', '/sys', '/snap']; if (!empty($_SERVER['DOCUMENT_ROOT'])) { - $blockedPrefixes[] = $_SERVER['DOCUMENT_ROOT']; - } + $blockedPrefixes[] = $_SERVER['DOCUMENT_ROOT']; + } foreach ($blockedPrefixes as $blockedPrefix) { - if (!$isValidLogFile) { - break; - } - if (str_starts_with($_POST['logFile'], $blockedPrefix)) { - $isValidLogFile = false; - } - } + if (!$isValidLogFile) { + break; + } + if (str_starts_with($_POST['logFile'], $blockedPrefix)) { + $isValidLogFile = false; + } + } if ($isValidLogFile) { $cfg->logDestination = $_POST['logFile']; - } + } else { $errors[] = _("The log file is empty or contains invalid characters! Valid characters are: a-z, A-Z, 0-9, /, ., _ and -. The file must end with '.log' or '.txt'."); - } + } } // password policies $cfg->passwordMinLength = $_POST['passwordMinLength']; @@ -315,8 +325,8 @@ if (isset($_POST['submitFormData'])) { else { $messages[] = _('You might need to restart your webserver for changes to take effect.'); } - } - } + } + } } } if (isset($_POST['sslCaCertDelete'])) { @@ -327,7 +337,7 @@ if (isset($_POST['submitFormData'])) { $matches = []; if (preg_match('/^ldaps:\/\/([a-zA-Z0-9_.-]+)(:(\d+))?$/', $_POST['serverurl'], $matches)) { $port = '636'; - if (isset($matches[3]) && !empty($matches[3])) { + if (!empty($matches[3])) { $port = $matches[3]; } $pemResult = getLDAPSSLCertificate($matches[1], $port); @@ -335,10 +345,12 @@ if (isset($_POST['submitFormData'])) { $messages[] = _('Imported certificate from server.'); $messages[] = _('You might need to restart your webserver for changes to take effect.'); $cfg->uploadSSLCaCert($pemResult); - } else { + } + else { $errors[] = _('Unable to import server certificate. Please use the upload function.'); } - } else { + } + else { $errors[] = _('Invalid server name. Please enter "server" or "server:port".'); } } @@ -355,19 +367,19 @@ if (isset($_POST['submitFormData'])) { $cfg->mailEncryption = $_POST['mailEncryption']; $cfg->mailServer = $_POST['mailServer']; if (!empty($cfg->mailServer) && !get_preg($cfg->mailServer, 'hostAndPort')) { - $errors[] = _('Please enter the mail server with host name and port.'); - } + $errors[] = _('Please enter the mail server with host name and port.'); + } $mailAttribute = strtolower($_POST['mailAttribute']); $mailBackupAttribute = strtolower($_POST['mailBackupAttribute']); if (empty($mailAttribute)) { $cfg->mailAttribute = LAMCfgMain::MAIL_ATTRIBUTE_DEFAULT; - } - elseif (preg_match('/^[a-z0-9_-]+$/', $mailAttribute)) { - $cfg->mailAttribute = $mailAttribute; - } + } + elseif (preg_match('/^[a-z0-9_-]+$/', $mailAttribute)) { + $cfg->mailAttribute = $mailAttribute; + } else { - $errors[] = _('The mail attributes are invalid.'); - } + $errors[] = _('The mail attributes are invalid.'); + } if (empty($mailBackupAttribute)) { $cfg->mailBackupAttribute = LAMCfgMain::MAIL_BACKUP_ATTRIBUTE_DEFAULT; } @@ -379,13 +391,13 @@ if (isset($_POST['submitFormData'])) { } } $cfg->errorReporting = $_POST['errorReporting']; - // module settings - $allModules = getAllModules(); - $moduleSettings = $cfg->getModuleSettings(); - foreach ($allModules as $module) { - $module->checkGlobalConfigOptions($moduleSettings, $messages, $errors); - } - $cfg->setModuleSettings($moduleSettings); + // module settings + $allModules = getAllModules(); + $moduleSettings = $cfg->getModuleSettings(); + foreach ($allModules as $module) { + $module->checkGlobalConfigOptions($moduleSettings, $messages, $errors); + } + $cfg->setModuleSettings($moduleSettings); // save settings if (isset($_POST['submit'])) { if (empty($errors)) { @@ -420,82 +432,82 @@ if (isset($_POST['submitFormData'])) { // check if config file is writable if (!$cfg->isWritable()) { - $row->add(new htmlStatusMessage('WARN', _('The config file is not writable.'), _('Your changes cannot be saved until you make the file writable for the webserver user.')), 12); + $row->add(new htmlStatusMessage('WARN', _('The config file is not writable.'), _('Your changes cannot be saved until you make the file writable for the webserver user.'))); } // database if (extension_loaded('PDO')) { - $row->add(new htmlSubTitle(_('Configuration storage')), 12); + $row->add(new htmlSubTitle(_('Configuration storage'))); $storageProviders = [ - _('Local file system') => LAMCfgMain::DATABASE_FILE_SYSTEM - ]; + _('Local file system') => LAMCfgMain::DATABASE_FILE_SYSTEM + ]; if (in_array('mysql', PDO::getAvailableDrivers())) { $storageProviders['MySQL'] = LAMCfgMain::DATABASE_MYSQL; } $storageProviderSelect = new htmlResponsiveSelect('configDatabaseType', $storageProviders, [$cfg->configDatabaseType], _('Database type'), '293'); $storageProviderSelect->setHasDescriptiveElements(true); $dbRowsToShow = [ - LAMCfgMain::DATABASE_FILE_SYSTEM => [], - LAMCfgMain::DATABASE_MYSQL => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'] - ]; + LAMCfgMain::DATABASE_FILE_SYSTEM => [], + LAMCfgMain::DATABASE_MYSQL => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'] + ]; $storageProviderSelect->setTableRowsToShow($dbRowsToShow); $dbRowsToHide = [ - LAMCfgMain::DATABASE_FILE_SYSTEM => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'], - LAMCfgMain::DATABASE_MYSQL => [] - ]; + LAMCfgMain::DATABASE_FILE_SYSTEM => ['configDatabaseServer', 'configDatabasePort', 'configDatabaseName', 'configDatabaseUser', 'configDatabasePassword'], + LAMCfgMain::DATABASE_MYSQL => [] + ]; $storageProviderSelect->setTableRowsToHide($dbRowsToHide); - $row->add($storageProviderSelect, 12); + $row->add($storageProviderSelect); $dbHost = new htmlResponsiveInputField(_('Database host'), 'configDatabaseServer', $cfg->configDatabaseServer, '273'); $dbHost->setRequired(true); - $row->add($dbHost, 12); + $row->add($dbHost); $dbPort = new htmlResponsiveInputField(_('Database port'), 'configDatabasePort', $cfg->configDatabasePort, '274'); - $row->add($dbPort, 12); + $row->add($dbPort); $dbName = new htmlResponsiveInputField(_('Database name'), 'configDatabaseName', $cfg->configDatabaseName, '276'); $dbName->setRequired(true); - $row->add($dbName, 12); + $row->add($dbName); $dbUser = new htmlResponsiveInputField(_('Database user'), 'configDatabaseUser', $cfg->configDatabaseUser, '275'); $dbUser->setRequired(true); - $row->add($dbUser, 12); + $row->add($dbUser); $dbPassword = new htmlResponsiveInputField(_('Database password'), 'configDatabasePassword', deobfuscateText($cfg->configDatabasePassword), '275'); $dbPassword->setRequired(true); $dbPassword->setIsPassword(true); - $row->add($dbPassword, 12); - } + $row->add($dbPassword); + } // license if (isLAMProVersion()) { - $row->add(new htmlSubTitle(_('Licence')), 12); - $row->add(new htmlResponsiveInputTextarea('license', implode("\n", $cfg->getLicenseLines()), '30', '10', _('Licence'), '287'), 12); + $row->add(new htmlSubTitle(_('Licence'))); + $row->add(new htmlResponsiveInputTextarea('license', implode("\n", $cfg->getLicenseLines()), '30', '10', _('Licence'), '287')); $warningOptions = [ - _('Screen') => LAMCfgMain::LICENSE_WARNING_SCREEN, - _('Email') => LAMCfgMain::LICENSE_WARNING_EMAIL, - _('Both') => LAMCfgMain::LICENSE_WARNING_ALL, - _('None') => LAMCfgMain::LICENSE_WARNING_NONE - ]; + _('Screen') => LAMCfgMain::LICENSE_WARNING_SCREEN, + _('Email') => LAMCfgMain::LICENSE_WARNING_EMAIL, + _('Both') => LAMCfgMain::LICENSE_WARNING_ALL, + _('None') => LAMCfgMain::LICENSE_WARNING_NONE + ]; $warningTypeSelect = new htmlResponsiveSelect('licenseWarningType', $warningOptions, [$cfg->getLicenseWarningType()], _('Expiration warning'), '288'); $warningTypeSelect->setHasDescriptiveElements(true); $warningTypeSelect->setSortElements(false); $warningTypeSelect->setTableRowsToHide([ - LAMCfgMain::LICENSE_WARNING_SCREEN => ['licenseEmailFrom', 'licenseEmailTo'], - LAMCfgMain::LICENSE_WARNING_NONE => ['licenseEmailFrom', 'licenseEmailTo'] - ]); + LAMCfgMain::LICENSE_WARNING_SCREEN => ['licenseEmailFrom', 'licenseEmailTo'], + LAMCfgMain::LICENSE_WARNING_NONE => ['licenseEmailFrom', 'licenseEmailTo'] + ]); $warningTypeSelect->setTableRowsToShow([ - LAMCfgMain::LICENSE_WARNING_EMAIL => ['licenseEmailFrom', 'licenseEmailTo'], - LAMCfgMain::LICENSE_WARNING_ALL => ['licenseEmailFrom', 'licenseEmailTo'] - ]); - $row->add($warningTypeSelect, 12); + LAMCfgMain::LICENSE_WARNING_EMAIL => ['licenseEmailFrom', 'licenseEmailTo'], + LAMCfgMain::LICENSE_WARNING_ALL => ['licenseEmailFrom', 'licenseEmailTo'] + ]); + $row->add($warningTypeSelect); $licenseFrom = new htmlResponsiveInputField(_('From address'), 'licenseEmailFrom', $cfg->licenseEmailFrom, '289'); $licenseFrom->setRequired(true); - $row->add($licenseFrom, 12); + $row->add($licenseFrom); $licenseTo = new htmlResponsiveInputField(_('To address'), 'licenseEmailTo', $cfg->licenseEmailTo, '290'); $licenseTo->setRequired(true); - $row->add($licenseTo, 12); + $row->add($licenseTo); $row->add(new htmlSpacer(null, '1rem'), true); } // security settings - $row->add(new htmlSubTitle(_("Security settings")), 12); + $row->add(new htmlSubTitle(_("Security settings"))); $options = [5, 10, 20, 30, 60, 90, 120, 240]; $row->add(new htmlResponsiveSelect('sessionTimeout', $options, [$cfg->sessionTimeout], _("Session timeout"), '238')); $hideLoginErrorDetails = ($cfg->hideLoginErrorDetails === 'true'); @@ -550,80 +562,82 @@ if (isset($_POST['submitFormData'])) { $serial = $sslCerts[$i]['serialNumber'] ?? ''; $validTo = $sslCerts[$i]['validTo_time_t'] ?? ''; if (get_preg($validTo, 'digit')) { - $date = DateTime::createFromFormat('U', $validTo, new DateTimeZone('UTC')); - if ($date !== false) { + $date = DateTime::createFromFormat('U', $validTo, new DateTimeZone('UTC')); + if ($date !== false) { $validTo = $date->format('Y-m-d'); - } - } + } + } $cn = $sslCerts[$i]['subject']['CN'] ?? ''; $delBtn = new htmlButton('deleteCert_' . $i, 'del.svg', true); $certsData[] = [ - new htmlOutputText($cn), - new htmlDiv(null, new htmlOutputText($validTo), ['nowrap']), - new htmlOutputText($serial), - $delBtn - ]; + new htmlOutputText($cn), + new htmlDiv(null, new htmlOutputText($validTo), ['nowrap']), + new htmlOutputText($serial), + $delBtn + ]; } $certsTable = new htmlResponsiveTable($certsTitles, $certsData); $certsTable->setCSSClasses(['text-left']); - $row->add($certsTable, 12); + $row->add($certsTable); } // password policy - $row->add(new htmlSubTitle(_("Password policy")), 12); + $row->add(new htmlSubTitle(_("Password policy"))); $optionsPwdLength = []; for ($i = 0; $i <= 50; $i++) { $optionsPwdLength[] = $i; } $options4 = [0, 1, 2, 3, 4]; - $row->add(new htmlResponsiveSelect('passwordMinLength', $optionsPwdLength, [$cfg->passwordMinLength], _('Minimum password length'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinLength', $optionsPwdLength, [$cfg->passwordMinLength], _('Minimum password length'), '242')); $row->addVerticalSpacer('1rem'); - $row->add(new htmlResponsiveSelect('passwordMinLower', $optionsPwdLength, [$cfg->passwordMinLower], _('Minimum lowercase characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinUpper', $optionsPwdLength, [$cfg->passwordMinUpper], _('Minimum uppercase characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinNumeric', $optionsPwdLength, [$cfg->passwordMinNumeric], _('Minimum numeric characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinSymbol', $optionsPwdLength, [$cfg->passwordMinSymbol], _('Minimum symbolic characters'), '242'), 12); - $row->add(new htmlResponsiveSelect('passwordMinClasses', $options4, [$cfg->passwordMinClasses], _('Minimum character classes'), '242'), 12); + $row->add(new htmlResponsiveSelect('passwordMinLower', $optionsPwdLength, [$cfg->passwordMinLower], _('Minimum lowercase characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinUpper', $optionsPwdLength, [$cfg->passwordMinUpper], _('Minimum uppercase characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinNumeric', $optionsPwdLength, [$cfg->passwordMinNumeric], _('Minimum numeric characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinSymbol', $optionsPwdLength, [$cfg->passwordMinSymbol], _('Minimum symbolic characters'), '242')); + $row->add(new htmlResponsiveSelect('passwordMinClasses', $options4, [$cfg->passwordMinClasses], _('Minimum character classes'), '242')); $row->addVerticalSpacer('1rem'); $rulesCountOptions = [_('all') => '-1', '3' => '3', '4' => '4']; $rulesCountSelect = new htmlResponsiveSelect('passwordRulesCount', $rulesCountOptions, [$cfg->checkedRulesCount], _('Number of rules that must match'), '246'); $rulesCountSelect->setHasDescriptiveElements(true); - $row->add($rulesCountSelect, 12); + $row->add($rulesCountSelect); $passwordMustNotContainUser = ($cfg->passwordMustNotContainUser === 'true'); - $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser', $passwordMustNotContainUser, _('Password must not contain user name'), '247'), 12); + $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContainUser', $passwordMustNotContainUser, _('Password must not contain user name'), '247')); $passwordMustNotContain3Chars = ($cfg->passwordMustNotContain3Chars === 'true'); - $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248'), 12); + $row->add(new htmlResponsiveInputCheckbox('passwordMustNotContain3Chars', $passwordMustNotContain3Chars, _('Password must not contain part of user/first/last name'), '248')); if (function_exists('curl_init')) { $row->addVerticalSpacer('1rem'); - $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249'), 12); + $row->add(new htmlResponsiveInputField(_('External password check'), 'externalPwdCheckUrl', $cfg->externalPwdCheckUrl, '249')); } // logging - $row->add(new htmlSubTitle(_("Logging")), 12); + $row->add(new htmlSubTitle(_("Logging"))); $levelOptions = [ - _("Debug") => LOG_DEBUG, - _("Notice") => LOG_NOTICE, - _("Warning") => LOG_WARNING, - _("Error") => LOG_ERR - ]; + _("Debug") => LOG_DEBUG, + _("Notice") => LOG_NOTICE, + _("Warning") => LOG_WARNING, + _("Error") => LOG_ERR + ]; $levelSelect = new htmlResponsiveSelect('logLevel', $levelOptions, [$cfg->logLevel], _("Log level"), '239'); $levelSelect->setHasDescriptiveElements(true); - $row->add($levelSelect, 12); + $row->add($levelSelect); $destinationOptions = [ - _("No logging") => "none", - _("System logging") => "syslog", - _("File") => 'file', - _("Remote") => 'remote' - ]; + _("No logging") => "none", + _("System logging") => "syslog", + _("File") => 'file', + _("Remote") => 'remote' + ]; $destinationSelected = 'file'; $destinationPath = $cfg->logDestination; $destinationRemote = ''; if ($cfg->logDestination == 'NONE') { $destinationSelected = 'none'; $destinationPath = ''; - } elseif ($cfg->logDestination == 'SYSLOG') { + } + elseif ($cfg->logDestination == 'SYSLOG') { $destinationSelected = 'syslog'; $destinationPath = ''; - } elseif (str_starts_with($cfg->logDestination, 'REMOTE')) { + } + elseif (str_starts_with($cfg->logDestination, 'REMOTE')) { $destinationSelected = 'remote'; $remoteParts = explode(':', $cfg->logDestination, 2); $destinationRemote = empty($remoteParts[1]) ? '' : $remoteParts[1]; @@ -631,31 +645,31 @@ if (isset($_POST['submitFormData'])) { } $logDestinationSelect = new htmlResponsiveSelect('logDestination', $destinationOptions, [$destinationSelected], _("Log destination"), '240'); $logDestinationSelect->setTableRowsToHide([ - 'none' => ['logFile', 'logRemote'], - 'syslog' => ['logFile', 'logRemote'], - 'remote' => ['logFile'], - 'file' => ['logRemote'] - ]); + 'none' => ['logFile', 'logRemote'], + 'syslog' => ['logFile', 'logRemote'], + 'remote' => ['logFile'], + 'file' => ['logRemote'] + ]); $logDestinationSelect->setTableRowsToShow([ - 'file' => ['logFile'], - 'remote' => ['logRemote'] - ]); + 'file' => ['logFile'], + 'remote' => ['logRemote'] + ]); $logDestinationSelect->setHasDescriptiveElements(true); - $row->add($logDestinationSelect, 12); - $row->add(new htmlResponsiveInputField(_('File'), 'logFile', $destinationPath), 12); - $row->add(new htmlResponsiveInputField(_('Remote server'), 'logRemote', $destinationRemote, '251'), 12); + $row->add($logDestinationSelect); + $row->add(new htmlResponsiveInputField(_('File'), 'logFile', $destinationPath)); + $row->add(new htmlResponsiveInputField(_('Remote server'), 'logRemote', $destinationRemote, '251')); $errorLogOptions = [ - _('PHP system setting') => LAMCfgMain::ERROR_REPORTING_SYSTEM, - _('default') => LAMCfgMain::ERROR_REPORTING_DEFAULT, - _('all') => LAMCfgMain::ERROR_REPORTING_ALL - ]; + _('PHP system setting') => LAMCfgMain::ERROR_REPORTING_SYSTEM, + _('default') => LAMCfgMain::ERROR_REPORTING_DEFAULT, + _('all') => LAMCfgMain::ERROR_REPORTING_ALL + ]; $errorLogSelect = new htmlResponsiveSelect('errorReporting', $errorLogOptions, [$cfg->errorReporting], _('PHP error reporting'), '244'); $errorLogSelect->setHasDescriptiveElements(true); $row->add($errorLogSelect); // mail options if (isLAMProVersion()) { - $row->add(new htmlSubTitle(_('Mail options')), 12); + $row->add(new htmlSubTitle(_('Mail options'))); $mailServer = new htmlResponsiveInputField(_("Mail server"), 'mailServer', $cfg->mailServer, '253'); $row->add($mailServer); $mailUser = new htmlResponsiveInputField(_("User name"), 'mailUser', $cfg->mailUser, '254'); @@ -664,10 +678,10 @@ if (isset($_POST['submitFormData'])) { $mailPassword->setIsPassword(true); $row->add($mailPassword); $mailEncryptionOptions = [ - 'TLS' => LAMCfgMain::SMTP_TLS, - 'SSL' => LAMCfgMain::SMTP_SSL, - _('None') => LAMCfgMain::SMTP_NONE - ]; + 'TLS' => LAMCfgMain::SMTP_TLS, + 'SSL' => LAMCfgMain::SMTP_SSL, + _('None') => LAMCfgMain::SMTP_NONE + ]; $selectedMailEncryption = empty($cfg->mailEncryption) ? LAMCfgMain::SMTP_TLS : $cfg->mailEncryption; $mailEncryptionSelect = new htmlResponsiveSelect('mailEncryption', $mailEncryptionOptions, [$selectedMailEncryption], _('Encryption protocol'), '256'); $mailEncryptionSelect->setHasDescriptiveElements(true); @@ -680,20 +694,20 @@ if (isset($_POST['submitFormData'])) { . "', '" . getSecurityTokenValue() . "', '" . _('Ok') . "', '" . _('Cancel') . "', '" . _('Test settings') . "')"); $row->addLabel(new htmlOutputText(" ", false)); $row->addField($mailTestButton); - $testDialogDivContent = new htmlResponsiveRow(); - $fromAddressInput = new htmlResponsiveInputField(_('From address'), 'testSmtpFrom', null, null, true); - $fromAddressInput->setType('email'); - $testDialogDivContent->add($fromAddressInput); - $toAddressInput = new htmlResponsiveInputField(_('To address'), 'testSmtpTo', null, null, true); + $testDialogDivContent = new htmlResponsiveRow(); + $fromAddressInput = new htmlResponsiveInputField(_('From address'), 'testSmtpFrom', null, null, true); + $fromAddressInput->setType('email'); + $testDialogDivContent->add($fromAddressInput); + $toAddressInput = new htmlResponsiveInputField(_('To address'), 'testSmtpTo', null, null, true); $toAddressInput->setType('email'); $testDialogDivContent->add($toAddressInput); - $testDialogDiv = new htmlDiv('smtpTestDialogDiv', $testDialogDivContent, ['hidden']); - $row->add($testDialogDiv); + $testDialogDiv = new htmlDiv('smtpTestDialogDiv', $testDialogDivContent, ['hidden']); + $row->add($testDialogDiv); } // webauthn management if (extension_loaded('PDO') - && in_array('sqlite', \PDO::getAvailableDrivers())) { + && in_array('sqlite', PDO::getAvailableDrivers())) { include_once __DIR__ . '/../../lib/webauthn.inc'; $webAuthnManager = new WebauthnManager(); try { @@ -701,7 +715,7 @@ if (isset($_POST['submitFormData'])) { if ($database->hasRegisteredCredentials()) { $row->add(new htmlSubTitle(_('WebAuthn devices'))); $webauthnSearchField = new htmlResponsiveInputField(_('User DN'), 'webauthn_searchTerm', null, '252'); - $row->add($webauthnSearchField, 12); + $row->add($webauthnSearchField); $row->addVerticalSpacer('0.5rem'); $row->add(new htmlButton('webauthn_search', _('Search')), 12, 12, 12, 'text-center'); $resultDiv = new htmlDiv('webauthn_results', new htmlOutputText(''), ['lam-webauthn-results', 'text-left']); @@ -713,48 +727,48 @@ if (isset($_POST['submitFormData'])) { } } catch (LAMException $e) { - logNewMessage(LOG_ERR, 'Webauthn error: ' . $e->getTitle() . ' ' . $e->getMessage()); - $row->add(new htmlStatusMessage('ERROR', $e->getTitle())); - } + logNewMessage(LOG_ERR, 'Webauthn error: ' . $e->getTitle() . ' ' . $e->getMessage()); + $row->add(new htmlStatusMessage('ERROR', $e->getTitle())); + } } - // module settings + // module settings $modules = getAllModules(); - $supportsGlobalCronJob = false; - foreach ($modules as $module) { - $supportsGlobalCronJob = $supportsGlobalCronJob || $module->supportsGlobalCronJob(); - $moduleOptions = $module->getGlobalConfigOptions($cfg->getModuleSettings()); - if (empty($moduleOptions)) { - continue; - } - $row->add(new htmlSubTitle($module->get_alias())); - foreach ($moduleOptions as $moduleOption) { - $row->add($moduleOption); - } + $supportsGlobalCronJob = false; + foreach ($modules as $module) { + $supportsGlobalCronJob = $supportsGlobalCronJob || $module->supportsGlobalCronJob(); + $moduleOptions = $module->getGlobalConfigOptions($cfg->getModuleSettings()); + if (empty($moduleOptions)) { + continue; + } + $row->add(new htmlSubTitle($module->get_alias())); + foreach ($moduleOptions as $moduleOption) { + $row->add($moduleOption); + } $row->addVerticalSpacer('3rem'); - } + } - // global cron job - if ($supportsGlobalCronJob) { + // global cron job + if ($supportsGlobalCronJob) { $row->add(new htmlSubTitle(_('Global cron job'))); - $cronCommand = dirname(__FILE__, 3) . '/lib/runCronJobs.sh all'; - $row->addLabel(new htmlOutputText('Cron command')); + $cronCommand = dirname(__FILE__, 3) . '/lib/runCronJobs.sh all'; + $row->addLabel(new htmlOutputText('Cron command')); $cmdGroup = new htmlGroup(); $cmdGroup->addElement(new htmlOutputText('0 0 * * * ' . $cronCommand)); $cmdGroup->addElement(new htmlSpacer('2rem', null)); $cmdGroup->addElement(new htmlHelpLink('294')); $row->addField($cmdGroup); - } + } // change master password $row->add(new htmlSubTitle(_("Change master password"))); $pwd1 = new htmlResponsiveInputField(_("New master password"), 'masterpassword', '', '235'); $pwd1->setIsPassword(true, false, true); - $row->add($pwd1, 12); + $row->add($pwd1); $pwd2 = new htmlResponsiveInputField(_("Reenter password"), 'masterpassword2', ''); $pwd2->setIsPassword(true, false, true); $pwd2->setSameValueFieldID('masterpassword'); - $row->add($pwd2, 12); + $row->add($pwd2); $row->addVerticalSpacer('3rem'); // buttons @@ -765,8 +779,8 @@ if (isset($_POST['submitFormData'])) { $buttonTable->addElement($saveButton); $buttonTable->addElement(new htmlSpacer('0.5rem', null)); $buttonTable->addElement(new htmlButton('cancel', _("Cancel"))); - $row->add($buttonTable, 12); - $row->add(new htmlHiddenInput('submitFormData', '1'), 12); + $row->add($buttonTable); + $row->add(new htmlHiddenInput('submitFormData', '1')); } $box = new htmlDiv(null, $row); diff --git a/lam/templates/setInitialPassword.php b/lam/templates/setInitialPassword.php index 80654310c..310473a86 100644 --- a/lam/templates/setInitialPassword.php +++ b/lam/templates/setInitialPassword.php @@ -87,7 +87,7 @@ if (isset($_POST['changePassword'])) { // check password strength $pwdPolicyResult = isValidConfigurationPassword($password1); if (!$pwdPolicyResult) { - $message = new htmlStatusMessage('ERROR', _('Please enter at least 8 characters including small and big letters, a number and a symbol.')); + $message = new htmlStatusMessage('ERROR', _('Please enter at least 8 characters including letters, a number and a symbol.')); printContent($message); exit(); } From 8a708160dd67c905736dba6ada6bcb373d46286d Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Jan 2025 07:46:51 +0100 Subject: [PATCH 06/11] #390 password policy for configuration --- lam/templates/config/confmain.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lam/templates/config/confmain.php b/lam/templates/config/confmain.php index d8a55979d..453e9a74a 100644 --- a/lam/templates/config/confmain.php +++ b/lam/templates/config/confmain.php @@ -27,7 +27,7 @@ use ServerProfilePersistenceManager; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2023 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -842,10 +842,13 @@ function checkInput(): array { $conf->setTwoFactorRememberDevicePassword($_POST['twoFactorRememberDevicePassword']); } // check if password was changed - if (isset($_POST['passwd1']) && ($_POST['passwd1'] != '')) { - if ($_POST['passwd1'] != $_POST['passwd2']) { + if (!empty($_POST['passwd1'])) { + if ($_POST['passwd1'] !== $_POST['passwd2']) { $errors[] = ["ERROR", _("Passwords are different!")]; } + elseif (!isValidConfigurationPassword($_POST['passwd1'])) { + $errors[] = ["ERROR", _('Profile password'), _('Please enter at least 8 characters including letters, a number and a symbol.')]; + } else { // set new password $conf->set_Passwd($_POST['passwd1']); From e53a7f20047049085a9778d945928a852a66facf Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Jan 2025 07:49:24 +0100 Subject: [PATCH 07/11] refactoring --- lam/templates/config/conflogin.php | 33 +++++++++++++++--------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/lam/templates/config/conflogin.php b/lam/templates/config/conflogin.php index a6737ade6..b9292b435 100644 --- a/lam/templates/config/conflogin.php +++ b/lam/templates/config/conflogin.php @@ -1,16 +1,16 @@ add($message, 12); + $row->add($message); $row->addVerticalSpacer('2rem'); } $box = new htmlResponsiveRow(); if (count($files) > 0) { - $box->add(new htmlOutputText(_("Please enter your password to change the server preferences:")), 12); + $box->add(new htmlOutputText(_("Please enter your password to change the server preferences:"))); $box->addVerticalSpacer('1.5rem'); $conf = new LAMCfgMain(); $selectedProfile = []; - $profilesExisting = false; $profiles = $files; if (!empty($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $files)) { $selectedProfile[] = $_COOKIE["lam_default_profile"]; @@ -146,18 +145,18 @@ printJsIncludes('../..'); else { $selectedProfile[] = $conf->default; } - $box->add(new htmlResponsiveSelect('filename', $profiles, $selectedProfile, _('Profile name')), 12); + $box->add(new htmlResponsiveSelect('filename', $profiles, $selectedProfile, _('Profile name'))); $passwordInput = new htmlResponsiveInputField(_('Password'), 'passwd', '', '200'); $passwordInput->setIsPassword(true); $passwordInput->setCSSClasses(['lam-initial-focus']); - $box->add($passwordInput, 12); + $box->add($passwordInput); $box->addVerticalSpacer('1rem'); $button = new htmlButton('submit', _("Ok")); $button->setCSSClasses(['lam-primary']); $box->addLabel($button); $box->add(new htmlOutputText(''), 0, 6); $box->addVerticalSpacer('1.5rem'); - $box->add(new htmlHorizontalLine(), 12); + $box->add(new htmlHorizontalLine()); $box->addVerticalSpacer('1.5rem'); } $manageLink = new htmlLink(_("Manage server profiles"), 'profmanage.php'); @@ -165,7 +164,7 @@ printJsIncludes('../..'); $boxDiv = new htmlDiv(null, $box); $boxDiv->setCSSClasses(['roundedShadowBox', 'limitWidth', 'text-center']); - $row->add($boxDiv, 12); + $row->add($boxDiv); // back link $row->addVerticalSpacer('2rem'); From 7493477f2d7aebf75198da0fdc582afc734acf43 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Jan 2025 07:51:54 +0100 Subject: [PATCH 08/11] refactoring --- lam/templates/config/conflogin.php | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lam/templates/config/conflogin.php b/lam/templates/config/conflogin.php index b9292b435..5e79fb6e9 100644 --- a/lam/templates/config/conflogin.php +++ b/lam/templates/config/conflogin.php @@ -74,16 +74,16 @@ for ($i = 0; $i < count($sessionKeys); $i++) { echo $_SESSION['header']; $serverProfilePersistenceManager = new ServerProfilePersistenceManager(); -$files = []; +$profileNames = []; try { - $files = $serverProfilePersistenceManager->getProfiles(); + $profileNames = $serverProfilePersistenceManager->getProfiles(); } catch (LAMException $e) { logNewMessage(LOG_ERR, 'Unable to read server profiles: ' . $e->getTitle()); } printHeaderContents(_("Login"), '../..'); -if (count($files) < 1) { +if (count($profileNames) < 1) { $message = new htmlStatusMessage('INFO', _("No server profiles found. Please create one.")); } ?> @@ -133,19 +133,18 @@ printJsIncludes('../..'); } $box = new htmlResponsiveRow(); - if (count($files) > 0) { + if (count($profileNames) > 0) { $box->add(new htmlOutputText(_("Please enter your password to change the server preferences:"))); $box->addVerticalSpacer('1.5rem'); $conf = new LAMCfgMain(); $selectedProfile = []; - $profiles = $files; - if (!empty($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $files)) { + if (!empty($_COOKIE["lam_default_profile"]) && in_array($_COOKIE["lam_default_profile"], $profileNames)) { $selectedProfile[] = $_COOKIE["lam_default_profile"]; } else { $selectedProfile[] = $conf->default; } - $box->add(new htmlResponsiveSelect('filename', $profiles, $selectedProfile, _('Profile name'))); + $box->add(new htmlResponsiveSelect('filename', $profileNames, $selectedProfile, _('Profile name'))); $passwordInput = new htmlResponsiveInputField(_('Password'), 'passwd', '', '200'); $passwordInput->setIsPassword(true); $passwordInput->setCSSClasses(['lam-initial-focus']); From 72698cdc2bbae0439d741645ecca343bb6ef0168 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Jan 2025 07:59:21 +0100 Subject: [PATCH 09/11] #390 check if password is set --- lam/lib/config.inc | 9 +++++++++ lam/templates/config/conflogin.php | 13 ++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index be516dc30..b34d80ffb 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -1466,6 +1466,15 @@ class LAMConfig { return "{CRYPT-SHA512}" . crypt($password, '$6$' . $salt) . " " . base64_encode($salt); } + /** + * Returns if the server profile has a password set. + * + * @return bool password is set + */ + public function hasPasswordSet(): bool { + return ($this->Passwd != null) && ($this->Passwd !== ''); + } + /** * Returns the LDAP suffix for the given account type * diff --git a/lam/templates/config/conflogin.php b/lam/templates/config/conflogin.php index 5e79fb6e9..9a5eaefd0 100644 --- a/lam/templates/config/conflogin.php +++ b/lam/templates/config/conflogin.php @@ -16,7 +16,7 @@ use ServerProfilePersistenceManager; /* This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) - Copyright (C) 2003 - 2023 Roland Gruber + Copyright (C) 2003 - 2025 Roland Gruber This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -75,8 +75,15 @@ echo $_SESSION['header']; $serverProfilePersistenceManager = new ServerProfilePersistenceManager(); $profileNames = []; +$profileNamesWithoutPassword = []; try { $profileNames = $serverProfilePersistenceManager->getProfiles(); + foreach ($profileNames as $profileName) { + $profile = $serverProfilePersistenceManager->loadProfile($profileName); + if (!$profile->hasPasswordSet()) { + $profileNamesWithoutPassword[] = $profileName; + } + } } catch (LAMException $e) { logNewMessage(LOG_ERR, 'Unable to read server profiles: ' . $e->getTitle()); @@ -86,6 +93,10 @@ printHeaderContents(_("Login"), '../..'); if (count($profileNames) < 1) { $message = new htmlStatusMessage('INFO', _("No server profiles found. Please create one.")); } +if ($profileNamesWithoutPassword !== []) { + $message = new htmlStatusMessage('INFO', _("There is at least one server profile without password. Please click on the manage server profiles link to set a password."), + htmlspecialchars(implode(', ', $profileNamesWithoutPassword))); +} ?> From 7f76ffc247af9f370648eb682a8dc289f698392a Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Jan 2025 17:47:34 +0100 Subject: [PATCH 10/11] #390 remove default password --- lam-packaging/docker/start.sh | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lam-packaging/docker/start.sh b/lam-packaging/docker/start.sh index bf5db8d9d..d2adf7db7 100755 --- a/lam-packaging/docker/start.sh +++ b/lam-packaging/docker/start.sh @@ -4,7 +4,7 @@ # This code is part of LDAP Account Manager (http://www.ldap-account-manager.org/) # Copyright (C) 2019 Felix Bartels -# 2019 - 2024 Roland Gruber +# 2019 - 2025 Roland Gruber # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -59,7 +59,6 @@ if [ "$LAM_SKIP_PRECONFIGURE" != "true" ]; then LAM_CONFIGURATION_PASSWORD="${LAM_CONFIGURATION_PASSWORD:-}" sed -i -f- /etc/ldap-account-manager/config.cfg <<- EOF - s|"password": "[^"]*"|"password": "${LAM_PASSWORD_SSHA}"|; s|"license": "[^"]*"|"license": "${LAM_LICENSE}"|; s|"configDatabaseType": "[^"]*"|"configDatabaseType": "${LAM_CONFIGURATION_DATABASE}"|; s|"configDatabaseServer": "[^"]*"|"configDatabaseServer": "${LAM_CONFIGURATION_HOST}"|; @@ -68,6 +67,9 @@ if [ "$LAM_SKIP_PRECONFIGURE" != "true" ]; then s|"configDatabaseUser": "[^"]*"|"configDatabaseUser": "${LAM_CONFIGURATION_USER}"|; s|"configDatabasePassword": "[^"]*"|"configDatabasePassword": "${LAM_CONFIGURATION_PASSWORD}"|; EOF + if ! grep -e '"password":' /etc/ldap-account-manager/config.cfg > /dev/null; then + sed -i "2i\ \ \"password\": \"${LAM_PASSWORD_SSHA}\"," /etc/ldap-account-manager/config.cfg + fi unset LAM_PASSWORD set +e @@ -82,12 +84,14 @@ EOF sed -i -f- /var/lib/ldap-account-manager/config/lam.conf <<- EOF s|"ServerURL": "[^"]*"|"ServerURL": "${LDAP_SERVER}"|; s|"Admins": "[^"]*"|"Admins": "${LDAP_ADMIN_USER}"|; - s|"Passwd": "[^"]*"|"Passwd": "${LAM_PASSWORD_SSHA}"|; s|"treeViewSuffix": "[^"]*"|"treeViewSuffix": "${LDAP_BASE_DN}"|; s|"defaultLanguage": "[^"]*"|"defaultLanguage": "${LAM_LANG}.utf8"|; s|"suffix_user": "[^"]*"|"suffix_user": "${LDAP_USERS_DN}"|; s|"suffix_group": "[^"]*"|"suffix_group": "${LDAP_GROUPS_DN}"|; EOF + if ! grep -e '"Passwd":' /var/lib/ldap-account-manager/config/lam.conf > /dev/null; then + sed -i "2i\ \ \"Passwd\": \"${LAM_PASSWORD_SSHA}\"," /var/lib/ldap-account-manager/config/lam.conf + fi fi From 66f5e7fcaf447e653cdf41e0e07ce059ccbd5918 Mon Sep 17 00:00:00 2001 From: Roland Gruber Date: Mon, 13 Jan 2025 19:37:19 +0100 Subject: [PATCH 11/11] refactoring --- lam/lib/config.inc | 2 +- lam/templates/config/mainmanage.php | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lam/lib/config.inc b/lam/lib/config.inc index b34d80ffb..6d8e535e2 100644 --- a/lam/lib/config.inc +++ b/lam/lib/config.inc @@ -58,7 +58,7 @@ include_once __DIR__ . '/2factor.inc'; */ function isValidConfigurationPassword(string $password): bool { return preg_match('/[a-zA-Z]/', $password) - && preg_match('/[0-9]/', $password) + && preg_match('/\d/', $password) && preg_match('/[^a-zA-Z0-9]/', $password) && (strlen($password) >= 8); } diff --git a/lam/templates/config/mainmanage.php b/lam/templates/config/mainmanage.php index 28b52468c..15c84b554 100644 --- a/lam/templates/config/mainmanage.php +++ b/lam/templates/config/mainmanage.php @@ -399,9 +399,8 @@ if (isset($_POST['submitFormData'])) { } $cfg->setModuleSettings($moduleSettings); // save settings - if (isset($_POST['submit'])) { - if (empty($errors)) { - try { + if (isset($_POST['submit']) && empty($errors)) { + try { $cfg->save(); $scriptTag = new htmlJavaScript('window.lam.dialog.showSuccessMessageAndRedirect("' . _("Your settings were successfully saved.") . '", "", "' . _('Ok') . '", "../login.php")'); parseHtml(null, $scriptTag, [], false, null); @@ -411,7 +410,6 @@ if (isset($_POST['submitFormData'])) { catch (LAMException $e) { $errors[] = $e->getTitle(); } - } } }