diff --git a/application/loginbasic.class.inc.php b/application/loginbasic.class.inc.php index 68f8eeaf6..8b0424768 100644 --- a/application/loginbasic.class.inc.php +++ b/application/loginbasic.class.inc.php @@ -80,6 +80,11 @@ class LoginBasic extends AbstractLoginFSMExtension { if (Session::Get('login_mode') == 'basic') { + $iOnExit = LoginWebPage::getIOnExit(); + if ($iOnExit === LoginWebPage::EXIT_RETURN) + { + return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM + } LoginWebPage::HTTP401Error(); } return LoginWebPage::LOGIN_FSM_CONTINUE; diff --git a/application/logindefault.class.inc.php b/application/logindefault.class.inc.php index 317261d7c..436af8436 100644 --- a/application/logindefault.class.inc.php +++ b/application/logindefault.class.inc.php @@ -79,7 +79,7 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte { self::ResetLoginSession(); $iOnExit = LoginWebPage::getIOnExit(); - if ($iOnExit == LoginWebPage::EXIT_RETURN) + if ($iOnExit === LoginWebPage::EXIT_RETURN) { return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM } @@ -95,6 +95,12 @@ class LoginDefaultAfter extends AbstractLoginFSMExtension implements iLogoutExte { if (!Session::IsSet('login_mode')) { + // N°6358 - if EXIT_RETURN was asked, send an error + if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) { + $iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS; + return LoginWebPage::LOGIN_FSM_ERROR; + } + // If no plugin validated the user, exit self::ResetLoginSession(); exit(); diff --git a/application/loginexternal.class.inc.php b/application/loginexternal.class.inc.php index a7055a14b..dd634a8a8 100644 --- a/application/loginexternal.class.inc.php +++ b/application/loginexternal.class.inc.php @@ -73,6 +73,11 @@ class LoginExternal extends AbstractLoginFSMExtension { if (Session::Get('login_mode') == 'external') { + $iOnExit = LoginWebPage::getIOnExit(); + if ($iOnExit === LoginWebPage::EXIT_RETURN) + { + return LoginWebPage::LOGIN_FSM_RETURN; // Error, exit FSM + } LoginWebPage::HTTP401Error(); } return LoginWebPage::LOGIN_FSM_CONTINUE; diff --git a/application/loginform.class.inc.php b/application/loginform.class.inc.php index d8e5bc8ee..22aa7380d 100644 --- a/application/loginform.class.inc.php +++ b/application/loginform.class.inc.php @@ -44,6 +44,10 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension exit; } + if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) { + return LoginWebPage::LOGIN_FSM_CONTINUE; + } + // No credentials yet, display the form $oPage = LoginWebPage::NewLoginWebPage(); $oPage->DisplayLoginForm($this->bForceFormOnError); diff --git a/application/loginwebpage.class.inc.php b/application/loginwebpage.class.inc.php index 0360d3ee5..79d499be0 100644 --- a/application/loginwebpage.class.inc.php +++ b/application/loginwebpage.class.inc.php @@ -1,5 +1,5 @@ output(); } - public static function ResetSession() + public static function ResetSession($bFullCleanup = false) { - // Unset all of the session variables. - Session::Unset('auth_user'); - Session::Unset('login_state'); - Session::Unset('can_logoff'); - Session::Unset('archive_mode'); - Session::Unset('impersonate_user'); + if ($bFullCleanup) { + // Unset all of the session variables. + foreach (array_keys($_SESSION) as $sKey) { + Session::Unset($sKey); + } + } else { + Session::Unset('auth_user'); + Session::Unset('login_state'); + Session::Unset('can_logoff'); + Session::Unset('archive_mode'); + Session::Unset('impersonate_user'); + } UserRights::_ResetSessionCache(); // If it's desired to kill the session, also delete the session cookie. // Note: This will destroy the session, and not just the session data! @@ -957,7 +964,7 @@ class LoginWebPage extends NiceWebPage } else { - if ($iOnExit == self::EXIT_RETURN) + if ($iOnExit === self::EXIT_RETURN) { return self::EXIT_CODE_PORTALUSERNOTAUTHORIZED; } @@ -1012,7 +1019,7 @@ class LoginWebPage extends NiceWebPage { if ($bMustBeAdmin && !UserRights::IsAdministrator()) { - if ($iOnExit == self::EXIT_RETURN) + if ($iOnExit === self::EXIT_RETURN) { return self::EXIT_CODE_MUSTBEADMIN; } @@ -1028,7 +1035,7 @@ class LoginWebPage extends NiceWebPage } $iRet = call_user_func(array(self::$sHandlerClass, 'ChangeLocation'), $sRequestedPortalId, $iOnExit); } - if ($iOnExit == self::EXIT_RETURN) + if ($iOnExit === self::EXIT_RETURN) { return $iRet; } diff --git a/composer.json b/composer.json index 3a23b4654..4ef754810 100644 --- a/composer.json +++ b/composer.json @@ -15,6 +15,7 @@ "combodo/tcpdf": "~6.4.4", "firebase/php-jwt": "~6.4.0", "guzzlehttp/guzzle": "^6.5.8", + "guzzlehttp/psr7": "~1.9.1", "laminas/laminas-mail": "^2.11", "laminas/laminas-servicemanager": "^3.5", "league/oauth2-google": "^3.0", diff --git a/datamodels/2.x/authent-cas/src/CASLoginExtension.php b/datamodels/2.x/authent-cas/src/CASLoginExtension.php index b092cac13..d5c58e1a4 100644 --- a/datamodels/2.x/authent-cas/src/CASLoginExtension.php +++ b/datamodels/2.x/authent-cas/src/CASLoginExtension.php @@ -49,6 +49,11 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte protected function OnReadCredentials(&$iErrorCode) { + if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) { + // Not allowed if not already connected + return LoginWebPage::LOGIN_FSM_CONTINUE; + } + if (empty(Session::Get('login_mode')) || Session::Get('login_mode') == static::LOGIN_MODE) { static::InitCASClient(); @@ -114,6 +119,10 @@ class CASLoginExtension extends AbstractLoginFSMExtension implements iLogoutExte if (Session::Get('login_mode') == static::LOGIN_MODE) { Session::Unset('phpCAS'); + if (LoginWebPage::getIOnExit() === LoginWebPage::EXIT_RETURN) { + // don't display the login page + return LoginWebPage::LOGIN_FSM_CONTINUE; + } if ($iErrorCode != LoginWebPage::EXIT_CODE_MISSINGLOGIN) { $oLoginWebPage = new LoginWebPage(); diff --git a/lib/autoload.php b/lib/autoload.php index f37a5b80e..743c6b6d6 100644 --- a/lib/autoload.php +++ b/lib/autoload.php @@ -2,6 +2,11 @@ // autoload.php @generated by Composer +if (PHP_VERSION_ID < 50600) { + echo 'Composer 2.3.0 dropped support for autoloading on PHP <5.6 and you are running '.PHP_VERSION.', please upgrade PHP or use Composer 2.2 LTS via "composer self-update --2.2". Aborting.'.PHP_EOL; + exit(1); +} + require_once __DIR__ . '/composer/autoload_real.php'; return ComposerAutoloaderInit5e7efdfe4e8f9526eb41991410b96239::getLoader(); diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php index 0cd6055d1..afef3fa2a 100644 --- a/lib/composer/ClassLoader.php +++ b/lib/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-var array + * @psalm-return array */ public function getClassMap() { diff --git a/lib/composer/InstalledVersions.php b/lib/composer/InstalledVersions.php index d50e0c9fc..c6b54af7b 100644 --- a/lib/composer/InstalledVersions.php +++ b/lib/composer/InstalledVersions.php @@ -21,12 +21,14 @@ use Composer\Semver\VersionParser; * See also https://getcomposer.org/doc/07-runtime.md#installed-versions * * To require its presence, you can require `composer-runtime-api ^2.0` + * + * @final */ class InstalledVersions { /** * @var mixed[]|null - * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array}|array{}|null + * @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array}|array{}|null */ private static $installed; @@ -37,7 +39,7 @@ class InstalledVersions /** * @var array[] - * @psalm-var array}> + * @psalm-var array}> */ private static $installedByVendor = array(); @@ -241,7 +243,7 @@ class InstalledVersions /** * @return array - * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string} + * @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool} */ public static function getRootPackage() { @@ -255,7 +257,7 @@ class InstalledVersions * * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect. * @return array[] - * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} + * @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} */ public static function getRawData() { @@ -278,7 +280,7 @@ class InstalledVersions * Returns the raw data of all installed.php which are currently loaded for custom implementations * * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ public static function getAllRawData() { @@ -301,7 +303,7 @@ class InstalledVersions * @param array[] $data A vendor/composer/installed.php data set * @return void * - * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array} $data + * @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array} $data */ public static function reload($data) { @@ -311,7 +313,7 @@ class InstalledVersions /** * @return array[] - * @psalm-return list}> + * @psalm-return list}> */ private static function getInstalled() { diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index 0dc29f123..c903ec99f 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -2,7 +2,7 @@ // autoload_classmap.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_files.php b/lib/composer/autoload_files.php index 7be757bea..ae02e5199 100644 --- a/lib/composer/autoload_files.php +++ b/lib/composer/autoload_files.php @@ -2,25 +2,25 @@ // autoload_files.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( '320cde22f66dd4f5d3fd621d3e88b98f' => $vendorDir . '/symfony/polyfill-ctype/bootstrap.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '5255c38a0faeba867671b61dfda6d864' => $vendorDir . '/paragonie/random_compat/lib/random.php', '023d27dca8066ef29e6739335ea73bad' => $vendorDir . '/symfony/polyfill-php70/bootstrap.php', - '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php', - '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', - 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php', '7e9bd612cc444b3eed788ebbe46263a0' => $vendorDir . '/laminas/laminas-zendframework-bridge/src/autoload.php', 'e69f7f6ee287b969198c3c9d6777bd38' => $vendorDir . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => $vendorDir . '/symfony/polyfill-intl-idn/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php', + 'bd9634f2d41831496de0d3dfe4c94881' => $vendorDir . '/symfony/polyfill-php56/bootstrap.php', 'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php', + '32dcc8afd4335739640db7d200c1971d' => $vendorDir . '/symfony/polyfill-apcu/bootstrap.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => $vendorDir . '/symfony/polyfill-iconv/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php', '2c102faa651ef8ea5874edb585946bce' => $vendorDir . '/swiftmailer/swiftmailer/lib/swift_required.php', ); diff --git a/lib/composer/autoload_namespaces.php b/lib/composer/autoload_namespaces.php index d12922d08..e6117c750 100644 --- a/lib/composer/autoload_namespaces.php +++ b/lib/composer/autoload_namespaces.php @@ -2,7 +2,7 @@ // autoload_namespaces.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_psr4.php b/lib/composer/autoload_psr4.php index ca8b4b9f6..651c9f0c1 100644 --- a/lib/composer/autoload_psr4.php +++ b/lib/composer/autoload_psr4.php @@ -2,7 +2,7 @@ // autoload_psr4.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 026b5cc7c..eb0882345 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -8,21 +8,21 @@ class ComposerStaticInit5e7efdfe4e8f9526eb41991410b96239 { public static $files = array ( '320cde22f66dd4f5d3fd621d3e88b98f' => __DIR__ . '/..' . '/symfony/polyfill-ctype/bootstrap.php', - '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '5255c38a0faeba867671b61dfda6d864' => __DIR__ . '/..' . '/paragonie/random_compat/lib/random.php', '023d27dca8066ef29e6739335ea73bad' => __DIR__ . '/..' . '/symfony/polyfill-php70/bootstrap.php', - '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php', - '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', - 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', + '0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php', '7e9bd612cc444b3eed788ebbe46263a0' => __DIR__ . '/..' . '/laminas/laminas-zendframework-bridge/src/autoload.php', 'e69f7f6ee287b969198c3c9d6777bd38' => __DIR__ . '/..' . '/symfony/polyfill-intl-normalizer/bootstrap.php', '25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php', 'f598d06aa772fa33d905e87be6398fb1' => __DIR__ . '/..' . '/symfony/polyfill-intl-idn/bootstrap.php', '7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php', + 'bd9634f2d41831496de0d3dfe4c94881' => __DIR__ . '/..' . '/symfony/polyfill-php56/bootstrap.php', 'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php', 'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php', '37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php', + '32dcc8afd4335739640db7d200c1971d' => __DIR__ . '/..' . '/symfony/polyfill-apcu/bootstrap.php', 'def43f6c87e4f8dfd0c9e1b1bab14fe8' => __DIR__ . '/..' . '/symfony/polyfill-iconv/bootstrap.php', + '667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php', '2c102faa651ef8ea5874edb585946bce' => __DIR__ . '/..' . '/swiftmailer/swiftmailer/lib/swift_required.php', ); diff --git a/lib/composer/include_paths.php b/lib/composer/include_paths.php index d4fb96718..af33c1491 100644 --- a/lib/composer/include_paths.php +++ b/lib/composer/include_paths.php @@ -2,7 +2,7 @@ // include_paths.php @generated by Composer -$vendorDir = dirname(dirname(__FILE__)); +$vendorDir = dirname(__DIR__); $baseDir = dirname($vendorDir); return array( diff --git a/lib/composer/installed.json b/lib/composer/installed.json index 0d5472a24..6a5aa95a6 100644 --- a/lib/composer/installed.json +++ b/lib/composer/installed.json @@ -543,17 +543,17 @@ }, { "name": "guzzlehttp/psr7", - "version": "1.9.0", - "version_normalized": "1.9.0.0", + "version": "1.9.1", + "version_normalized": "1.9.1.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318" + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", - "reference": "e98e3e6d4f86621a9b75f623996e6bbdeb4b9318", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/e4490cabc77465aaee90b20cfc9a770f8c04be6b", + "reference": "e4490cabc77465aaee90b20cfc9a770f8c04be6b", "shasum": "" }, "require": { @@ -571,13 +571,8 @@ "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, - "time": "2022-06-20T21:43:03+00:00", + "time": "2023-04-17T16:00:37+00:00", "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, "installation-source": "dist", "autoload": { "files": [ @@ -636,7 +631,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/1.9.0" + "source": "https://github.com/guzzle/psr7/tree/1.9.1" }, "funding": [ { diff --git a/lib/guzzlehttp/psr7/.github/workflows/ci.yml b/lib/guzzlehttp/psr7/.github/workflows/ci.yml index eda7dceb5..0850470e0 100644 --- a/lib/guzzlehttp/psr7/.github/workflows/ci.yml +++ b/lib/guzzlehttp/psr7/.github/workflows/ci.yml @@ -6,7 +6,7 @@ on: jobs: build: name: Build - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: max-parallel: 10 matrix: @@ -21,11 +21,7 @@ jobs: extensions: mbstring - name: Checkout code - uses: actions/checkout@v2 - - - name: Mimic PHP 8.0 - run: composer config platform.php 8.0.999 - if: matrix.php > 8 + uses: actions/checkout@v3 - name: Install dependencies run: composer update --no-interaction --no-progress diff --git a/lib/guzzlehttp/psr7/.github/workflows/integration.yml b/lib/guzzlehttp/psr7/.github/workflows/integration.yml index 3c31f9ef2..a55a256ed 100644 --- a/lib/guzzlehttp/psr7/.github/workflows/integration.yml +++ b/lib/guzzlehttp/psr7/.github/workflows/integration.yml @@ -4,14 +4,13 @@ on: pull_request: jobs: - build: name: Test - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: max-parallel: 10 matrix: - php: ['7.2', '7.3', '7.4', '8.0'] + php: ['7.2', '7.3', '7.4', '8.0', '8.1'] steps: - name: Set up PHP @@ -21,7 +20,7 @@ jobs: coverage: none - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Download dependencies uses: ramsey/composer-install@v1 diff --git a/lib/guzzlehttp/psr7/.github/workflows/static.yml b/lib/guzzlehttp/psr7/.github/workflows/static.yml index ab4d68ba3..f00351b68 100644 --- a/lib/guzzlehttp/psr7/.github/workflows/static.yml +++ b/lib/guzzlehttp/psr7/.github/workflows/static.yml @@ -6,11 +6,11 @@ on: jobs: php-cs-fixer: name: PHP-CS-Fixer - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 diff --git a/lib/guzzlehttp/psr7/CHANGELOG.md b/lib/guzzlehttp/psr7/CHANGELOG.md index b4fdf3c68..9b2b65cdb 100644 --- a/lib/guzzlehttp/psr7/CHANGELOG.md +++ b/lib/guzzlehttp/psr7/CHANGELOG.md @@ -9,6 +9,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Unreleased +## 1.9.1 - 2023-04-17 + +### Fixed + +- Fixed header validation issue + ## 1.9.0 - 2022-06-20 ### Added diff --git a/lib/guzzlehttp/psr7/composer.json b/lib/guzzlehttp/psr7/composer.json index 0e36920db..2607f22d4 100644 --- a/lib/guzzlehttp/psr7/composer.json +++ b/lib/guzzlehttp/psr7/composer.json @@ -61,11 +61,6 @@ "GuzzleHttp\\Tests\\Psr7\\": "tests/" } }, - "extra": { - "branch-alias": { - "dev-master": "1.9-dev" - } - }, "config": { "preferred-install": "dist", "sort-packages": true, diff --git a/lib/guzzlehttp/psr7/src/MessageTrait.php b/lib/guzzlehttp/psr7/src/MessageTrait.php index 0ac8663da..0bbd63e0d 100644 --- a/lib/guzzlehttp/psr7/src/MessageTrait.php +++ b/lib/guzzlehttp/psr7/src/MessageTrait.php @@ -226,12 +226,9 @@ trait MessageTrait throw new \InvalidArgumentException('Header name can not be empty.'); } - if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/', $header)) { + if (! preg_match('/^[a-zA-Z0-9\'`#$%&*+.^_|~!-]+$/D', $header)) { throw new \InvalidArgumentException( - sprintf( - '"%s" is not valid header name', - $header - ) + sprintf('"%s" is not valid header name.', $header) ); } } @@ -263,8 +260,10 @@ trait MessageTrait // Clients must not send a request with line folding and a server sending folded headers is // likely very rare. Line folding is a fairly obscure feature of HTTP/1.1 and thus not accepting // folding is not likely to break any legitimate use case. - if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/', $value)) { - throw new \InvalidArgumentException(sprintf('"%s" is not valid header value', $value)); + if (! preg_match('/^[\x20\x09\x21-\x7E\x80-\xFF]*$/D', $value)) { + throw new \InvalidArgumentException( + sprintf('"%s" is not valid header value.', $value) + ); } } } diff --git a/pages/ajax.searchform.php b/pages/ajax.searchform.php index 5b27b4806..adc9e9a44 100644 --- a/pages/ajax.searchform.php +++ b/pages/ajax.searchform.php @@ -18,10 +18,7 @@ try $oKPI->ComputeAndReport('Data model loaded'); $oKPI = new ExecutionKPI(); - if (LoginWebPage::EXIT_CODE_OK != LoginWebPage::DoLoginEx('backoffice', false, LoginWebPage::EXIT_RETURN)) - { - throw new SecurityException('You must be logged in'); - } + LoginWebPage::DoLogin(); $sParams = utils::ReadParam('params', '', false, 'raw_data'); if (!$sParams) diff --git a/pages/logoff.php b/pages/logoff.php index 134c1543c..2620b04b6 100644 --- a/pages/logoff.php +++ b/pages/logoff.php @@ -96,5 +96,11 @@ if ($bLoginDebug) IssueLog::Info("--> Display logout page"); } +LoginWebPage::ResetSession(true); +if ($bLoginDebug) { + $sSessionLog = session_id().' '.utils::GetSessionLog(); + IssueLog::Info("SESSION: $sSessionLog"); +} + $oPage = LoginWebPage::NewLoginWebPage(); $oPage->DisplayLogoutPage($bPortal); diff --git a/synchro/synchro_exec.php b/synchro/synchro_exec.php index ba0f901ae..3946b66b7 100644 --- a/synchro/synchro_exec.php +++ b/synchro/synchro_exec.php @@ -105,11 +105,38 @@ if (utils::IsModeCLI()) else { require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - //N°6022 - Make synchro scripts work by http via token authentication with SYNCHRO scopes $oCtx = new ContextTag(ContextTag::TAG_SYNCHRO); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed -} + LoginWebPage::ResetSession(true); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + if ($iRet !== LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + $oP->p("Missing parameter 'auth_user'"); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + $oP->p("Missing parameter 'auth_pwd'"); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + $oP->p('Invalid login'); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + $oP->p('Portal user is not allowed'); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); + break; + + default: + $oP->p("Unknown authentication error (retCode=$iRet)"); + } + $oP->output(); + exit -1; + } $bSimulate = (utils::ReadParam('simulate', '0', true) == '1'); $sDataSourcesList = ReadMandatoryParam($oP, 'data_sources', 'raw_data'); // May contain commas diff --git a/synchro/synchro_import.php b/synchro/synchro_import.php index 935d824de..1ecfbdbd9 100644 --- a/synchro/synchro_import.php +++ b/synchro/synchro_import.php @@ -282,10 +282,38 @@ if (utils::IsModeCLI()) else { require_once APPROOT.'/application/loginwebpage.class.inc.php'; - //N°6022 - Make synchro scripts work by http via token authentication with SYNCHRO scopes $oCtx = new ContextTag(ContextTag::TAG_SYNCHRO); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed + LoginWebPage::ResetSession(true); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + if ($iRet !== LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + $oP->p("Missing parameter 'auth_user'"); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + $oP->p("Missing parameter 'auth_pwd'"); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + $oP->p('Invalid login'); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + $oP->p('Portal user is not allowed'); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); + break; + + default: + $oP->p("Unknown authentication error (retCode=$iRet)"); + } + $oP->output(); + exit -1; + } $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); } diff --git a/tests/php-unit-tests/ItopDataTestCase.php b/tests/php-unit-tests/ItopDataTestCase.php index 151792fed..f8ff9dcc6 100644 --- a/tests/php-unit-tests/ItopDataTestCase.php +++ b/tests/php-unit-tests/ItopDataTestCase.php @@ -456,7 +456,7 @@ class ItopDataTestCase extends ItopTestCase /** @var DBObjectSet $oSet */ $oSet = $oUser->Get('profile_list'); $oSet->AddObject($oUserProfile); - $oUser = $this->updateObject('UserLocal', $oUser->GetKey(), array( + $oUser = $this->updateObject('User', $oUser->GetKey(), array( 'profile_list' => $oSet, )); $this->debug("Updated {$oUser->GetName()} ({$oUser->GetKey()})"); diff --git a/tests/php-unit-tests/unitary-tests/webservices/CliResetSessionTest.php b/tests/php-unit-tests/unitary-tests/webservices/CliResetSessionTest.php new file mode 100644 index 000000000..cd03f3cfd --- /dev/null +++ b/tests/php-unit-tests/unitary-tests/webservices/CliResetSessionTest.php @@ -0,0 +1,257 @@ +sConfigTmpBackupFile = tempnam(sys_get_temp_dir(), "config_"); + MetaModel::GetConfig()->WriteToFile($this->sConfigTmpBackupFile); + + $this->sLogin = "rest-user-".date('dmYHis'); + $this->CreateTestOrganization(); + + $this->sCookieFile = tempnam(sys_get_temp_dir(), 'jsondata_'); + + $this->sUrl = \MetaModel::GetConfig()->Get('app_root_url'); + + $oRestProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'REST Services User'), true); + $oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true); + + if (is_object($oRestProfile) && is_object($oAdminProfile)) { + $oUser = $this->CreateUser($this->sLogin, $oRestProfile->GetKey(), $this->sPassword); + $this->sUserId = $oUser->GetKey(); + $this->AddProfileToUser($oUser, $oAdminProfile->GetKey()); + } + } + + protected function tearDown(): void { + parent::tearDown(); + + if (! is_null($this->sConfigTmpBackupFile) && is_file($this->sConfigTmpBackupFile)){ + //put config back + $sConfigPath = MetaModel::GetConfig()->GetLoadedFile(); + @chmod($sConfigPath, 0770); + $oConfig = new Config($this->sConfigTmpBackupFile); + $oConfig->WriteToFile($sConfigPath); + @chmod($sConfigPath, 0440); + unlink($this->sConfigTmpBackupFile); + } + + if (!empty($this->sCookieFile)) { + unlink($this->sCookieFile); + } + } + + protected function AddLoginMode($sLoginMode){ + @chmod(MetaModel::GetConfig()->GetLoadedFile(), 0770); + $aAllowedLoginTypes = MetaModel::GetConfig()->GetAllowedLoginTypes(); + if (! in_array($sLoginMode, $aAllowedLoginTypes)){ + $aAllowedLoginTypes[] = $sLoginMode; + MetaModel::GetConfig()->SetAllowedLoginTypes($aAllowedLoginTypes); + MetaModel::GetConfig()->WriteToFile(); + } + @chmod(MetaModel::GetConfig()->GetLoadedFile(), 0440); + } + + protected function SetLoginModes($aAllowedLoginTypes){ + @chmod(MetaModel::GetConfig()->GetLoadedFile(), 0770); + MetaModel::GetConfig()->SetAllowedLoginTypes($aAllowedLoginTypes); + MetaModel::GetConfig()->WriteToFile(); + @chmod(MetaModel::GetConfig()->GetLoadedFile(), 0440); + } + + public function RestProvider(){ + return [ + 'nominal / no login_mode forced' => [ + 'sConfiguredLoginModes' => 'form|external|basic', + 'sForcedLoginMode' => null, + ], + 'nominal / form forced' => [ + 'sConfiguredLoginModes' => 'form|external|basic', + 'sForcedLoginMode' => 'form', + ], + 'nominal / external forced' => [ + 'sConfiguredLoginModes' => 'form|external|basic', + 'sForcedLoginMode' => 'external', + ], + 'nominal / basic forced' => [ + 'sConfiguredLoginModes' => 'form|external|basic', + 'sForcedLoginMode' => 'basic', + ], + 'nominal / url forced' => [ + 'sConfiguredLoginModes' => 'form|external|basic|url', + 'sForcedLoginMode' => 'url', + ], + 'nominal / cas forced' => [ + 'sConfiguredLoginModes' => 'form|external|basic|cas', + 'sForcedLoginMode' => 'cas', + ], + ]; + } + + /** + * @dataProvider RestProvider + * @param $aLoginModes + * @param $sForcedLoginMode + * + * @return void + */ + public function testRest($sConfiguredLoginModes=null, $sForcedLoginMode=null, $sExpectedFailHttpCode="200"){ + if (! is_null($sConfiguredLoginModes)){ + $this->SetLoginModes(explode('|', $sConfiguredLoginModes)); + } + + $sJsonGetContent = <<sUserId, + "output_fields": "id" +} +JSON; + $aPostFields = [ + 'version' => '1.2', + 'auth_user' => $this->sLogin, + 'auth_pwd' => $this->sPassword, + 'json_data' => $sJsonGetContent, + ]; + list($iHttpCode, $sJson) = $this->CallRestApi($aPostFields); + $this->assertEquals(200, $iHttpCode); + + $aJson = json_decode($sJson, true); + $this->assertTrue(is_array($aJson), $sJson); + $this->assertEquals("0", $aJson['code'], $sJson); + + //2nd call to REST API made with previous session cookie + //no need to pass auth_user/auth_pwd + $aPostFields = [ + 'version' => '1.2', + 'json_data' => $sJsonGetContent, + ]; + list($iHttpCode, $sJson) = $this->CallRestApi($aPostFields, $sForcedLoginMode); + $this->debug($sJson); + $this->assertEquals($sExpectedFailHttpCode, $iHttpCode); + if ($iHttpCode === "200") { + $this->assertEquals('{"code":1,"message":"Error: Invalid login"}', $sJson); + } + } + + public function OtherCliProvider(){ + return [ + 'import' => [ 'webservices/import.php' ], + 'synchro_exec' => [ 'synchro/synchro_exec.php' ], + 'synchro_import' => [ 'synchro/synchro_import.php' ], + ]; + } + + /** + * @dataProvider OtherCliProvider + */ + public function testImport($sUri){ + $sJsonGetContent = <<sUserId, + "output_fields": "id" +} +JSON; + $aPostFields = [ + 'version' => '1.2', + 'auth_user' => $this->sLogin, + 'auth_pwd' => $this->sPassword, + 'json_data' => $sJsonGetContent, + ]; + list($iHttpCode, $sOutput) = $this->CallRestApi($aPostFields); + $this->assertEquals(200, $iHttpCode); + + $aJson = json_decode($sOutput, true); + $this->assertTrue(is_array($aJson), $sOutput); + $this->assertEquals("0", $aJson['code'], $sOutput); + + //2nd call to REST API made with previous session cookie + //no need to pass auth_user/auth_pwd + $aPostFields = [ + 'version' => '1.2', + 'json_data' => $sJsonGetContent, + ]; + list($iHttpCode, $sOutput) = $this->CallRestApi($aPostFields, null, $sUri); + $this->debug($sOutput); + $this->assertEquals("200", $iHttpCode); + $this->assertContains("Invalid login", $sOutput); + } + + /** + * @param $aPostFields + * + * @return array($iHttpCode, $sBody) + */ + private function CallRestApi($aPostFields, $sForcedLoginMode=null, $sUri='webservices/rest.php'): array { + $ch = curl_init(); + + curl_setopt ($ch, CURLOPT_COOKIEJAR, $this->sCookieFile); + curl_setopt ($ch, CURLOPT_COOKIEFILE, $this->sCookieFile); + + $sUrl = "$this->sUrl/$sUri"; + if (!is_null($sForcedLoginMode)){ + $sUrl .= "?login_mode=$sForcedLoginMode"; + } + curl_setopt($ch, CURLOPT_URL, $sUrl); + curl_setopt($ch, CURLOPT_POST, 1);// set post data to true + curl_setopt($ch, CURLOPT_POSTFIELDS, $aPostFields); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); + curl_setopt($ch, CURLOPT_HEADER, 1); + // Force disable of certificate check as most of dev / test env have a self-signed certificate + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0); + + $sResponse = curl_exec($ch); + /** $sResponse example + * "HTTP/1.1 200 OK + Date: Wed, 07 Jun 2023 05:00:40 GMT + Server: Apache/2.4.29 (Ubuntu) + Set-Cookie: itop-2e83d2e9b00e354fdc528621cac532ac=q7ldcjq0rvbn33ccr9q8u8e953; path=/ + */ + //var_dump($sResponse); + $iHeaderSize = curl_getinfo($ch,CURLINFO_HEADER_SIZE); + $sBody = substr($sResponse, $iHeaderSize); + + //$iHttpCode = intval(curl_getinfo($ch, CURLINFO_HTTP_CODE)); + if (preg_match('/HTTP.* (\d*) /', $sResponse, $aMatches)){ + $sHttpCode = $aMatches[1]; + } else { + $sHttpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + } + curl_close ($ch); + + return array($sHttpCode, $sBody); + } +} diff --git a/webservices/import.php b/webservices/import.php index b88da870e..6e02bae94 100644 --- a/webservices/import.php +++ b/webservices/import.php @@ -259,7 +259,36 @@ if (utils::IsModeCLI()) else { require_once(APPROOT.'/application/loginwebpage.class.inc.php'); - LoginWebPage::DoLogin(); // Check user rights and prompt if needed + LoginWebPage::ResetSession(true); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + if ($iRet !== LoginWebPage::EXIT_CODE_OK) { + switch ($iRet) { + case LoginWebPage::EXIT_CODE_MISSINGLOGIN: + $oP->p("Missing parameter 'auth_user'"); + break; + + case LoginWebPage::EXIT_CODE_MISSINGPASSWORD: + $oP->p("Missing parameter 'auth_pwd'"); + break; + + case LoginWebPage::EXIT_CODE_WRONGCREDENTIALS: + $oP->p('Invalid login'); + break; + + case LoginWebPage::EXIT_CODE_PORTALUSERNOTAUTHORIZED: + $oP->p('Portal user is not allowed'); + break; + + case LoginWebPage::EXIT_CODE_NOTAUTHORIZED: + $oP->p('This user is not authorized to use the web services. (The profile REST Services User is required to access the REST web services)'); + break; + + default: + $oP->p("Unknown authentication error (retCode=$iRet)"); + } + $oP->output(); + exit -1; + } $sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data'); } diff --git a/webservices/rest.php b/webservices/rest.php index 61ab77db0..8a63321b7 100644 --- a/webservices/rest.php +++ b/webservices/rest.php @@ -97,10 +97,12 @@ try $oKPI->ComputeAndReport('Data model loaded'); - $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); // Starting with iTop 2.2.0 portal users are no longer allowed to access the REST/JSON API - $oKPI->ComputeAndReport('User login'); - - if ($iRet == LoginWebPage::EXIT_CODE_OK) + // N°6358 - force credentials for REST calls + LoginWebPage::ResetSession(true); + $iRet = LoginWebPage::DoLogin(false, false, LoginWebPage::EXIT_RETURN); + $oKPI->ComputeAndReport('User login'); + + if ($iRet == LoginWebPage::EXIT_CODE_OK) { // Extra validation of the profile if ((MetaModel::GetConfig()->Get('secure_rest_services') == true) && !UserRights::HasProfile('REST Services User')) @@ -111,7 +113,7 @@ try } if ($iRet != LoginWebPage::EXIT_CODE_OK) { - switch($iRet) + switch($iRet) { case LoginWebPage::EXIT_CODE_MISSINGLOGIN: throw new Exception("Missing parameter 'auth_user'", RestResult::MISSING_AUTH_USER);