From 540e02937050e39fc50b69a0c22735c7233c1597 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Mon, 13 Jan 2025 14:42:16 -0800 Subject: [PATCH 1/4] EnvName attribute --- src/attributes/EnvName.php | 24 ++++++++++++++++++++++++ src/helpers/App.php | 20 ++++++++++++++++---- 2 files changed, 40 insertions(+), 4 deletions(-) create mode 100644 src/attributes/EnvName.php diff --git a/src/attributes/EnvName.php b/src/attributes/EnvName.php new file mode 100644 index 00000000000..818ad52b6e0 --- /dev/null +++ b/src/attributes/EnvName.php @@ -0,0 +1,24 @@ + + * @since 5.6.0 + */ +#[Attribute] +class EnvName +{ + public function __construct( + public readonly string $name, + ) {} +} diff --git a/src/helpers/App.php b/src/helpers/App.php index 7586ed09f80..96295a3ba67 100644 --- a/src/helpers/App.php +++ b/src/helpers/App.php @@ -9,6 +9,7 @@ use Closure; use Craft; +use craft\attributes\EnvName; use craft\behaviors\SessionBehavior; use craft\cache\FileCache; use craft\config\DbConfig; @@ -160,12 +161,23 @@ public static function envConfig(string $class, ?string $envPrefix = null): arra continue; } - $propName = $prop->getName(); - $envName = $envPrefix . strtoupper(StringHelper::toSnakeCase($propName)); - $envValue = static::env($envName); + $envName = null; + + foreach ($prop->getAttributes(EnvName::class) as $attribute) { + /** @var EnvName $envName */ + $envName = $attribute->newInstance(); + $envName = $envName->name; + break; + } + + if (!$envName) { + $envName = strtoupper(StringHelper::toSnakeCase($prop->getName())); + } + + $envValue = static::env(sprintf('%s%s', $envPrefix, $envName)); if ($envValue !== null) { - $envConfig[$propName] = $envValue; + $envConfig[$prop->getName()] = $envValue; } } From 0a3e96ab4635201483fb6e690839355932e8cc8e Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Mon, 13 Jan 2025 14:42:35 -0800 Subject: [PATCH 2/4] disable2fa setting --- src/config/GeneralConfig.php | 43 +++++++++++++++++++++++++++++ src/controllers/UsersController.php | 31 +++++++++++---------- src/web/Application.php | 10 ++++--- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/config/GeneralConfig.php b/src/config/GeneralConfig.php index 91bbe8666eb..e9d7a62a754 100644 --- a/src/config/GeneralConfig.php +++ b/src/config/GeneralConfig.php @@ -9,6 +9,7 @@ use Closure; use Craft; +use craft\attributes\EnvName; use craft\helpers\ConfigHelper; use craft\helpers\DateTimeHelper; use craft\helpers\Localization; @@ -944,6 +945,24 @@ class GeneralConfig extends BaseConfig */ public bool $devMode = false; + /** + * @var bool Whether two-step verification features should be disabled. + * + * ::: code + * ```php Static Config + * ->disable2fa() + * ``` + * ```shell Environment Override + * CRAFT_DISABLE_2FA=true + * ``` + * ::: + * + * @group Users + * @since 5.6.0 + */ + #[EnvName('DISABLE_2FA')] + public bool $disable2fa = false; + /** * @var string[]|string|null Array of plugin handles that should be disabled, regardless of what the project config says. * @@ -4316,6 +4335,30 @@ public function devMode(bool $value = true): self return $this; } + /** + * Whether two-step verification features should be disabled. + * + * ::: code + * ```php Static Config + * ->disable2fa() + * ``` + * ```shell Environment Override + * CRAFT_DISABLE_2FA=true + * ``` + * ::: + * + * @group Users + * @param bool $value + * @return self + * @see $disable2fa + * @since 5.6.0 + */ + public function disable2fa(bool $value = true): self + { + $this->disable2fa = $value; + return $this; + } + /** * Array of plugin handles that should be disabled, regardless of what the project config says. * diff --git a/src/controllers/UsersController.php b/src/controllers/UsersController.php index cd63726e606..1b251f45eeb 100644 --- a/src/controllers/UsersController.php +++ b/src/controllers/UsersController.php @@ -248,24 +248,27 @@ public function actionLogin(): ?Response $duration = $generalConfig->userSessionDuration; } - // if user has an active 2SV method, move on to that - $authService = Craft::$app->getAuth(); $userSession = Craft::$app->getUser(); - if (!empty($authService->getActiveMethods($user))) { - $authService->setUser($user, $duration); - if ($this->request->getIsSiteRequest() && !$this->request->getAcceptsJson()) { - $loginPath = $generalConfig->getLoginPath(); - if (!$loginPath) { - $authService->setUser(null); - throw new InvalidConfigException('User requires two-step verification, but the loginPath config setting is disabled.'); + // if user has an active 2SV method, move on to that + if (!$generalConfig->disable2fa) { + $authService = Craft::$app->getAuth(); + if ($authService->hasActiveMethod($user)) { + $authService->setUser($user, $duration); + + if ($this->request->getIsSiteRequest() && !$this->request->getAcceptsJson()) { + $loginPath = $generalConfig->getLoginPath(); + if (!$loginPath) { + $authService->setUser(null); + throw new InvalidConfigException('User requires two-step verification, but the loginPath config setting is disabled.'); + } + return $this->redirect(UrlHelper::siteUrl($loginPath, [ + 'verify' => 1, + ])); } - return $this->redirect(UrlHelper::siteUrl($loginPath, [ - 'verify' => 1, - ])); - } - return $this->runAction('auth-form'); + return $this->runAction('auth-form'); + } } // if we're impersonating, pass the user we're impersonating to the complete method diff --git a/src/web/Application.php b/src/web/Application.php index 9bb624a1b40..85a0f24ca60 100644 --- a/src/web/Application.php +++ b/src/web/Application.php @@ -294,10 +294,12 @@ public function handleRequest($request, bool $skipSpecialHandling = false): Base if (!$userSession->getIsGuest()) { // See if the user is expected to have 2FA enabled - $auth = $this->getAuth(); - $user = $userSession->getIdentity(); - if ($auth->is2faRequired($user) && !$auth->hasActiveMethod($user)) { - return $this->runAction('users/setup-2fa'); + if (!$generalConfig->disable2fa) { + $auth = $this->getAuth(); + $user = $userSession->getIdentity(); + if ($auth->is2faRequired($user) && !$auth->hasActiveMethod($user)) { + return $this->runAction('users/setup-2fa'); + } } if ($isCpRequest && !$this->getCanTestEditions()) { From 13a8cd4e5dd7f6bae709cd6bcfa2dafff636a338 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Mon, 13 Jan 2025 14:45:46 -0800 Subject: [PATCH 3/4] Release notes [ci skip] --- CHANGELOG-WIP.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 8bbe57bc6a7..f5a79bffa69 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -70,10 +70,12 @@ - It’s now possible to reference custom field handles in element queries’ `where` params. ([#16318](https://github.com/craftcms/cms/pull/16318)) - Number fields’ scalar values now return an integer if Decimals is set to `0`, and a number formatted with the correct decimal points when using MySQL. ([16369](https://github.com/craftcms/cms/issues/16369)) - Added support for specifying the current site via an `X-Craft-Site` header set to a site ID or handle. ([#16367](https://github.com/craftcms/cms/pull/16367)) +- Added the `disable2fa` config setting. ([#16426](https://github.com/craftcms/cms/pull/16426)) - Added support for defining redirects from `config/redirects.php`. ([#16355](https://github.com/craftcms/cms/pull/16355)) - Deprecated the `ucfirst` Twig filter. `capitalize` should be used instead. ### Extensibility +- Added `craft\attributes\EnvName`. - Added `craft\base\ConfigurableComponentInterface::getReadOnlySettingsHtml()`. ([#16265](https://github.com/craftcms/cms/pull/16265)) - Added `craft\base\CrossSiteCopyableFieldInterface`. ([#14056](https://github.com/craftcms/cms/pull/14056)) - Added `craft\base\Element::EVENT_DEFINE_ALT_ACTIONS`. ([#16294](https://github.com/craftcms/cms/pull/16294)) @@ -135,6 +137,7 @@ - `GuzzleHttp\Client` is now instantiated via `Craft::createObject()`. ([#16366](https://github.com/craftcms/cms/pull/16366)) - `craft\elements\NestedElementManager::getIndexHtml()` now supports passing `defaultSort` in the `$config` array. ([#16236](https://github.com/craftcms/cms/discussions/16236)) - `craft\elements\conditions\entries\MatrixFieldConditionRule` is now an alias of `FieldConditionRule`. +- `craft\helpers\App::envConfig()` now checks for a `craft\attributes\EnvName` attribute on public properties, which can be used to override the environment variable name (sans prefix) that is associated with the property. - `craft\helpers\Cp::elementIndexHtml()` now supports passing `defaultSort` in the `$config` array, when `sources` is `null`. ([#16236](https://github.com/craftcms/cms/discussions/16236)) - `craft\helpers\Cp::fieldHtml()` now supports passing an `actionMenuItems` array in the config. ([#16415](https://github.com/craftcms/cms/pull/16415)) - `craft\helpers\DateTimeHelper::humanDuration()` now has a `$language` argument. ([#16332](https://github.com/craftcms/cms/pull/16332)) From 89c375ff72071d096de9daa262149cc69bc90a05 Mon Sep 17 00:00:00 2001 From: brandonkelly Date: Mon, 13 Jan 2025 14:53:18 -0800 Subject: [PATCH 4/4] Newline --- src/attributes/EnvName.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/attributes/EnvName.php b/src/attributes/EnvName.php index 818ad52b6e0..71e2d250fa4 100644 --- a/src/attributes/EnvName.php +++ b/src/attributes/EnvName.php @@ -20,5 +20,6 @@ class EnvName { public function __construct( public readonly string $name, - ) {} + ) { + } }