From 271ee5ffb141924ffb1aac218548a7b3ff805fa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Espi=C3=A9?= Date: Tue, 13 Oct 2026 10:26:48 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B09954=20-=20Login-basic=20incompatible=20?= =?UTF-8?q?with=20OAuth2=20server=20(#1077)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/loginbasic.class.inc.php | 67 +++++++------ .../application/LoginFSM/LoginBasicTest.php | 93 +++++++++++++++++++ 2 files changed, 130 insertions(+), 30 deletions(-) create mode 100644 tests/php-unit-tests/unitary-tests/application/LoginFSM/LoginBasicTest.php diff --git a/application/loginbasic.class.inc.php b/application/loginbasic.class.inc.php index f0b88b5104..796bd3f7ae 100644 --- a/application/loginbasic.class.inc.php +++ b/application/loginbasic.class.inc.php @@ -5,7 +5,7 @@ use Combodo\iTop\Application\Helper\Session; /** * Class LoginBasic * - * @copyright Copyright (C) 2010-2024 Combodo SAS + * @copyright Copyright (C) 2010-2026 Combodo SAS * @license http://opensource.org/licenses/AGPL-3.0 */ @@ -24,11 +24,7 @@ class LoginBasic extends AbstractLoginFSMExtension protected function OnModeDetection(&$iErrorCode) { if (!Session::IsSet('login_mode')) { - if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { - Session::Set('login_mode', 'basic'); - } elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { - Session::Set('login_mode', 'basic'); - } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + if (!is_null($this->GetAuthUserAndPassword())) { Session::Set('login_mode', 'basic'); } } @@ -86,38 +82,49 @@ class LoginBasic extends AbstractLoginFSMExtension return LoginWebPage::LOGIN_FSM_CONTINUE; } - private function GetAuthUserAndPassword() + /** + * @return array|null [$sAuthUser, $sAuthPwd] + */ + private function GetAuthUserAndPassword(): ?array { - $sAuthUser = ''; - $sAuthPwd = null; $sAuthorization = ''; if (isset($_SERVER['HTTP_AUTHORIZATION']) && !empty($_SERVER['HTTP_AUTHORIZATION'])) { $sAuthorization = $_SERVER['HTTP_AUTHORIZATION']; } elseif (isset($_SERVER['REDIRECT_HTTP_AUTHORIZATION']) && !empty($_SERVER['REDIRECT_HTTP_AUTHORIZATION'])) { $sAuthorization = $_SERVER['REDIRECT_HTTP_AUTHORIZATION']; + } elseif (isset($_SERVER['PHP_AUTH_USER'])) { + $sAuthUser = $_SERVER['PHP_AUTH_USER']; + // Unfortunately, the RFC is not clear about the encoding... + // IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8 + // So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base + if (!LoginWebPage::LooksLikeUTF8($sAuthUser)) { + // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 + // Supposed to be harmless in case of a plain ASCII string... + $sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser); + } + $sAuthPwd = $_SERVER['PHP_AUTH_PW']; + if (!LoginWebPage::LooksLikeUTF8($sAuthPwd)) { + // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 + // Supposed to be harmless in case of a plain ASCII string... + $sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd); + } + + return [$sAuthUser, $sAuthPwd]; } - if (!empty($sAuthorization)) { - list($sAuthUser, $sAuthPwd) = explode(':', base64_decode(substr($sAuthorization, 6))); - } else { - if (isset($_SERVER['PHP_AUTH_USER'])) { - $sAuthUser = $_SERVER['PHP_AUTH_USER']; - // Unfortunately, the RFC is not clear about the encoding... - // IE and FF supply the user and password encoded in ISO-8859-1 whereas Chrome provides them encoded in UTF-8 - // So let's try to guess if it's an UTF-8 string or not... fortunately all encodings share the same ASCII base - if (!LoginWebPage::LooksLikeUTF8($sAuthUser)) { - // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 - // Supposed to be harmless in case of a plain ASCII string... - $sAuthUser = iconv('iso-8859-1', 'utf-8', $sAuthUser); - } - $sAuthPwd = $_SERVER['PHP_AUTH_PW']; - if (!LoginWebPage::LooksLikeUTF8($sAuthPwd)) { - // Does not look like and UTF-8 string, try to convert it from iso-8859-1 to UTF-8 - // Supposed to be harmless in case of a plain ASCII string... - $sAuthPwd = iconv('iso-8859-1', 'utf-8', $sAuthPwd); - } - } + if (empty($sAuthorization)) { + return null; } - return [$sAuthUser, $sAuthPwd]; + + // Decode without Bearer + if (strncasecmp($sAuthorization, 'Basic ', 6) !== 0) { + return null; + } + $sDecodedAuthorisation = base64_decode(substr($sAuthorization, 6)); + if (str_contains($sDecodedAuthorisation, ':')) { + return explode(':', $sDecodedAuthorisation); + } + + return null; } } diff --git a/tests/php-unit-tests/unitary-tests/application/LoginFSM/LoginBasicTest.php b/tests/php-unit-tests/unitary-tests/application/LoginFSM/LoginBasicTest.php new file mode 100644 index 0000000000..c55b95233b --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/application/LoginFSM/LoginBasicTest.php @@ -0,0 +1,93 @@ +ListSupportedLoginModes(); + + // Then + $this->assertEquals(['basic'], $aActual); + } + + public function testOnModeDetectionForLoginPass() + { + // Given + $oLoginBasic = new LoginBasic(); + $_SERVER['HTTP_AUTHORIZATION'] = 'Basic '.base64_encode("Login:BasicTest"); + + // When + $_SESSION = []; + $iErrorCode = 0; + $sActualRes = $oLoginBasic->LoginAction(LoginWebPage::LOGIN_STATE_MODE_DETECTION, $iErrorCode); + + // Then + $this->assertEquals('basic', $_SESSION['login_mode'], 'Login mode should have detected basic mode'); + $this->assertEquals(LoginWebPage::LOGIN_FSM_CONTINUE, $sActualRes, 'No Login FSM error should have been detected'); + } + + public function testOnModeDetectionForRedirect() + { + // Given + $oLoginBasic = new LoginBasic(); + $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] = 'Basic '.base64_encode('Login:BasicTest'); + + // When + $_SESSION = []; + $iErrorCode = 0; + $sActualRes = $oLoginBasic->LoginAction(LoginWebPage::LOGIN_STATE_MODE_DETECTION, $iErrorCode); + + // Then + $this->assertEquals('basic', $_SESSION['login_mode'], 'Login mode should have detected basic mode'); + $this->assertEquals(LoginWebPage::LOGIN_FSM_CONTINUE, $sActualRes, 'No Login FSM error should have been detected'); + } + + public function testOnModeDetectionForBasicLoginPassword() + { + // Given + $oLoginBasic = new LoginBasic(); + $_SERVER['PHP_AUTH_USER'] = 'Login'; + $_SERVER['PHP_AUTH_PW'] = 'Passwd'; + + // When + $_SESSION = []; + $iErrorCode = 0; + $sActualRes = $oLoginBasic->LoginAction(LoginWebPage::LOGIN_STATE_MODE_DETECTION, $iErrorCode); + + // Then + $this->assertEquals('basic', $_SESSION['login_mode'], 'Login mode should have detected basic mode'); + $this->assertEquals(LoginWebPage::LOGIN_FSM_CONTINUE, $sActualRes, 'No Login FSM error should have been detected'); + } + + public function testOnModeDetectionForBearerTokenNotDetectedAsBasic() + { + // Given + $oLoginBasic = new LoginBasic(); + $_SERVER['HTTP_AUTHORIZATION'] = 'Bearer '.base64_encode('Login:BasicTest'); + + // When + $_SESSION = []; + $iErrorCode = 0; + $sActualRes = $oLoginBasic->LoginAction(LoginWebPage::LOGIN_STATE_MODE_DETECTION, $iErrorCode); + + // Then + $this->assertFalse(Session::IsSet('login_mode'), 'No basic login mode should have been detected'); + $this->assertEquals(LoginWebPage::LOGIN_FSM_CONTINUE, $sActualRes, 'No Login FSM error should have been detected'); + } +}