diff --git a/CHANGELOG-WIP.md b/CHANGELOG-WIP.md index 659da2a7d43..eee31bd9007 100644 --- a/CHANGELOG-WIP.md +++ b/CHANGELOG-WIP.md @@ -2,3 +2,7 @@ ### Extensibility - Added `craft\base\RequestTrait::getIsWebRequest()`. ([#15690](https://github.com/craftcms/cms/pull/15690)) +- Added `craft\filters\BasicHttpAuthLogin`. ([#15720](https://github.com/craftcms/cms/pull/15720)) +- Added `craft\filters\BasicHttpAuthStatic`. ([#15720](https://github.com/craftcms/cms/pull/15720)) +- Added `craft\filters\SiteFilterTrait::$enabled`. ([#15720](https://github.com/craftcms/cms/pull/15720)) +- Deprecated the `enableBasicHttpAuth` config setting. `craft\filters\BasicHttpAuthLogin` should be used instead. ([#15720](https://github.com/craftcms/cms/pull/15720)) diff --git a/src/config/GeneralConfig.php b/src/config/GeneralConfig.php index 240bd6cde96..258c6a3f38d 100644 --- a/src/config/GeneralConfig.php +++ b/src/config/GeneralConfig.php @@ -1071,6 +1071,7 @@ class GeneralConfig extends BaseConfig * * @group Security * @since 3.5.0 + * @deprecated in 4.13.0. [[\craft\filters\BasicHttpAuthLogin]] should be used instead. */ public bool $enableBasicHttpAuth = false; diff --git a/src/filters/BasicHttpAuthLogin.php b/src/filters/BasicHttpAuthLogin.php new file mode 100644 index 00000000000..411782873b9 --- /dev/null +++ b/src/filters/BasicHttpAuthLogin.php @@ -0,0 +1,57 @@ + + * @since 4.13.0 + */ +class BasicHttpAuthLogin extends HttpBasicAuth +{ + use SiteFilterTrait, BasicHttpAuthTrait; + + /** + * @inheritdoc + */ + public $realm; + + /** + * @inheritdoc + */ + public function __construct($config = []) + { + parent::__construct($config + [ + 'auth' => [$this, 'auth'], + 'realm' => Craft::$app->getSystemName(), + ]); + } + + protected function auth($username, $password): ?IdentityInterface + { + if (!$username || !$password) { + return null; + } + + $user = User::find()->username($username)->one(); + $identity = $user?->findIdentity($user->id); + + if ($identity && Craft::$app->getSecurity()->validatePassword($password, $identity->password)) { + return $identity; + } + + return null; + } +} diff --git a/src/filters/BasicHttpAuthStatic.php b/src/filters/BasicHttpAuthStatic.php new file mode 100644 index 00000000000..9bca52ea7c9 --- /dev/null +++ b/src/filters/BasicHttpAuthStatic.php @@ -0,0 +1,73 @@ + + * @since 4.13.0 + */ +class BasicHttpAuthStatic extends HttpBasicAuth +{ + use SiteFilterTrait, BasicHttpAuthTrait; + + public ?string $username = null; + public ?string $password = null; + + /** + * @inheritdoc + */ + public $realm; + + /** + * @inheritDoc + */ + public function __construct($config = []) + { + parent::__construct($config + [ + 'username' => App::env('CRAFT_HTTP_BASIC_AUTH_USERNAME'), + 'password' => App::env('CRAFT_HTTP_BASIC_AUTH_PASSWORD'), + 'realm' => Craft::$app->getSystemName(), + ]); + } + + /** + * @inheritDoc + */ + public function beforeAction($action): bool + { + if (!$this->username || !$this->password) { + throw new InvalidConfigException('Basic authentication is not configured.'); + } + + $currentUser = Craft::$app->getUser()->getIdentity(); + + if ($currentUser) { + return true; + } + + list($username, $password) = Craft::$app->getRequest()->getAuthCredentials(); + + if ($username === $this->username && $password === $this->password) { + return true; + } + + $response = Craft::$app->getResponse(); + $this->challenge($response); + $this->handleFailure($response); + + return false; + } +} diff --git a/src/filters/BasicHttpAuthTrait.php b/src/filters/BasicHttpAuthTrait.php new file mode 100644 index 00000000000..186f07b85d2 --- /dev/null +++ b/src/filters/BasicHttpAuthTrait.php @@ -0,0 +1,33 @@ + + * @since 4.13.0 + */ +trait BasicHttpAuthTrait +{ + /** + * Detach behavior and manually handle exception so error handling + * isn't called recursively when already handling an exception (e.g. 404s) + */ + public function handleFailure($response): void + { + $this->detach(); + + Craft::$app->getErrorHandler()->handleException( + new UnauthorizedHttpException('Your request was made with invalid credentials.'), + ); + } +} diff --git a/src/filters/SiteFilterTrait.php b/src/filters/SiteFilterTrait.php index a64efec946f..fc4df080d14 100644 --- a/src/filters/SiteFilterTrait.php +++ b/src/filters/SiteFilterTrait.php @@ -21,6 +21,12 @@ */ trait SiteFilterTrait { + /** + * @var bool Whether the filter should be enabled. + * @since 4.13.0 + */ + public bool $enabled = true; + private null|array $siteIds = null; protected function isActive(mixed $action): bool @@ -29,7 +35,7 @@ protected function isActive(mixed $action): bool return false; } - return $this->isCurrentSiteActive(); + return $this->enabled && $this->isCurrentSiteActive(); } protected function setSite(null|array|int|string|Site $value): void