diff --git a/_build/data/transport.core.context_settings.php b/_build/data/transport.core.context_settings.php index b9a3227b9f7..48de8059f29 100644 --- a/_build/data/transport.core.context_settings.php +++ b/_build/data/transport.core.context_settings.php @@ -1,4 +1,5 @@ newObject(modContextSetting::class); @@ -21,3 +22,13 @@ 'area' => 'system', 'editedon' => NULL, ], '', true, true); +$collection['2'] = $xpdo->newObject(modContextSetting::class); +$collection['2']->fromArray([ + 'context_key' => 'mgr', + 'key' => 'anonymous_sessions', + 'value' => true, + 'xtype' => 'combo-boolean', + 'namespace' => 'core', + 'area' => 'session', + 'editedon' => null, +], '', true, true); diff --git a/_build/test/Tests/Model/Security/AnonymousSessionsContextTest.php b/_build/test/Tests/Model/Security/AnonymousSessionsContextTest.php new file mode 100644 index 00000000000..bc991007e94 --- /dev/null +++ b/_build/test/Tests/Model/Security/AnonymousSessionsContextTest.php @@ -0,0 +1,236 @@ +originalSystemAnonymousSessions = $this->modx->getOption('anonymous_sessions'); + $this->modx->setOption('anonymous_sessions', false); + $this->modx->_systemConfig['anonymous_sessions'] = false; + + $mgrSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + ]); + if (!$mgrSetting) { + $mgrSetting = $this->modx->newObject(modContextSetting::class); + $mgrSetting->fromArray([ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + 'value' => true, + 'xtype' => 'combo-boolean', + 'namespace' => 'core', + 'area' => 'session', + ], '', true, true); + $this->assertTrue((bool)$mgrSetting->save(), 'Could not create mgr anonymous_sessions fixture.'); + $this->createdMgrSetting = true; + $this->originalMgrAnonymousSessions = null; + } else { + $this->originalMgrAnonymousSessions = $mgrSetting->get('value'); + $mgrSetting->set('value', true); + $this->assertTrue((bool)$mgrSetting->save(), 'Could not enable mgr anonymous_sessions fixture.'); + } + + $webSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_DEFAULT, + 'key' => 'anonymous_sessions', + ]); + if ($webSetting) { + $webSetting->remove(); + } + $webSetting = $this->modx->newObject(modContextSetting::class); + $webSetting->fromArray([ + 'context_key' => modContext::CONTEXT_DEFAULT, + 'key' => 'anonymous_sessions', + 'value' => false, + 'xtype' => 'combo-boolean', + 'namespace' => 'core', + 'area' => 'session', + ], '', true, true); + $this->assertTrue((bool)$webSetting->save(), 'Could not create web anonymous_sessions fixture.'); + $this->createdWebSetting = true; + + $this->refreshContextSettings(); + } + + /** + * @after + */ + public function tearDownFixtures() + { + if ($this->createdMgrSetting) { + $mgrSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + ]); + if ($mgrSetting) { + $mgrSetting->remove(); + } + } elseif ($this->originalMgrAnonymousSessions !== null) { + $mgrSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + ]); + if ($mgrSetting) { + $mgrSetting->set('value', $this->originalMgrAnonymousSessions); + $mgrSetting->save(); + } + } + + if ($this->createdWebSetting) { + $webSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_DEFAULT, + 'key' => 'anonymous_sessions', + ]); + if ($webSetting) { + $webSetting->remove(); + } + } + + $this->modx->setOption('anonymous_sessions', $this->originalSystemAnonymousSessions); + $this->modx->_systemConfig['anonymous_sessions'] = $this->originalSystemAnonymousSessions; + $this->refreshContextSettings(); + $this->modx->reloadContext(modContext::CONTEXT_DEFAULT); + + parent::tearDownFixtures(); + } + + public function testManagerContextOverridesSystemAnonymousSessionsFalse() + { + $this->assertTrue( + $this->activateContext(modContext::CONTEXT_MANAGER), + 'Could not activate mgr context.' + ); + $this->assertTrue( + $this->modx->paramValueIsTrue( + ['anonymous_sessions' => $this->modx->getOption('anonymous_sessions')], + 'anonymous_sessions' + ), + 'mgr must enable anonymous_sessions so login can start a session without a cookie.' + ); + } + + public function testWebContextCanDisableAnonymousSessions() + { + $this->assertTrue( + $this->activateContext(modContext::CONTEXT_MANAGER), + 'Could not activate mgr context before returning to web.' + ); + $this->assertTrue( + $this->activateContext(modContext::CONTEXT_DEFAULT), + 'Could not activate web context.' + ); + $this->assertFalse( + $this->modx->paramValueIsTrue( + ['anonymous_sessions' => $this->modx->getOption('anonymous_sessions')], + 'anonymous_sessions' + ), + 'web context should be able to disable anonymous_sessions independently of mgr.' + ); + } + + public function testUpgradeScriptEnsuresManagerAnonymousSessions() + { + $mgrSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + ]); + if ($mgrSetting) { + $mgrSetting->remove(); + } + $this->refreshContextSettings(); + + $modx = $this->modx; + $upgradeScript = dirname(__DIR__, 5) . '/setup/includes/upgrades/common/3.3.0-mgr-anonymous-sessions.php'; + include $upgradeScript; + + $mgrSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + ]); + $this->assertInstanceOf(modContextSetting::class, $mgrSetting); + $this->assertTrue( + $this->modx->paramValueIsTrue(['value' => $mgrSetting->get('value')], 'value'), + 'Upgrade must create mgr anonymous_sessions=Yes.' + ); + + $mgrSetting->set('value', false); + $this->assertTrue((bool)$mgrSetting->save()); + + include $upgradeScript; + + $mgrSetting = $this->modx->getObject(modContextSetting::class, [ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + ]); + $this->assertTrue( + $this->modx->paramValueIsTrue(['value' => $mgrSetting->get('value')], 'value'), + 'Upgrade must restore mgr anonymous_sessions=Yes if it was disabled.' + ); + $this->createdMgrSetting = true; + } + + private function activateContext(string $contextKey): bool + { + if ($this->modx->context instanceof modContext && $this->modx->context->get('key') === $contextKey) { + return (bool)$this->modx->reloadContext($contextKey); + } + + return (bool)$this->modx->switchContext($contextKey, true); + } + + private function refreshContextSettings(): void + { + $this->modx->cacheManager->refresh([ + 'context_settings' => [ + 'contexts' => [modContext::CONTEXT_MANAGER, modContext::CONTEXT_DEFAULT], + ], + ]); + unset($this->modx->contexts[modContext::CONTEXT_MANAGER], $this->modx->contexts[modContext::CONTEXT_DEFAULT]); + } +} diff --git a/core/lexicon/en/setting.inc.php b/core/lexicon/en/setting.inc.php index b98e375f93d..8c9b766f87e 100644 --- a/core/lexicon/en/setting.inc.php +++ b/core/lexicon/en/setting.inc.php @@ -98,7 +98,7 @@ $_lang['setting_allow_tv_eval_desc'] = 'Select this option to enable or disable eval in TV bindings. If this option is set to no, the code/value will just be handled as regular text.'; $_lang['setting_anonymous_sessions'] = 'Anonymous Sessions'; -$_lang['setting_anonymous_sessions_desc'] = 'If disabled, only authenticated users will have access to a PHP session. This can reduce overhead for anonymous users and the load they impose on a MODX site if they do not need access to a unique session. If session_enabled is false, this setting has no effect as sessions would never be available.'; +$_lang['setting_anonymous_sessions_desc'] = 'When authentication or other persistence features that require a PHP Session are not needed, set this to “No.” The Manager (mgr) Context must set this to “Yes” to operate correctly. Note that in multi-Context setups, the system-wide value for this setting can be overridden at the Context level.'; $_lang['setting_archive_with'] = 'Force PCLZip Archives'; $_lang['setting_archive_with_desc'] = 'If true, will use PCLZip instead of ZipArchive as the zip extension. Turn this on if you are getting extractTo errors or are having problems with unzipping in Package Management.'; diff --git a/setup/includes/upgrades/common/3.3.0-mgr-anonymous-sessions.php b/setup/includes/upgrades/common/3.3.0-mgr-anonymous-sessions.php new file mode 100644 index 00000000000..7d3a6118d40 --- /dev/null +++ b/setup/includes/upgrades/common/3.3.0-mgr-anonymous-sessions.php @@ -0,0 +1,43 @@ + modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', +]; + +$setting = $modx->getObject(modContextSetting::class, $criteria); +if (!$setting) { + $setting = $modx->newObject(modContextSetting::class); + $setting->fromArray([ + 'context_key' => modContext::CONTEXT_MANAGER, + 'key' => 'anonymous_sessions', + 'value' => true, + 'xtype' => 'combo-boolean', + 'namespace' => 'core', + 'area' => 'session', + ], '', true, true); + $setting->save(); +} elseif (!$modx->paramValueIsTrue(['value' => $setting->get('value')], 'value')) { + $setting->set('value', true); + $setting->save(); +} + +if ($modx->cacheManager) { + $modx->cacheManager->refresh([ + 'context_settings' => ['contexts' => [modContext::CONTEXT_MANAGER]], + ]); +} diff --git a/setup/includes/upgrades/mysql/3.3.0-pl.php b/setup/includes/upgrades/mysql/3.3.0-pl.php new file mode 100644 index 00000000000..026b8a2a7ee --- /dev/null +++ b/setup/includes/upgrades/mysql/3.3.0-pl.php @@ -0,0 +1,12 @@ +