diff --git a/en/02_Developer_Guides/09_Security/04_Sudo_Mode.md b/en/02_Developer_Guides/09_Security/04_Sudo_Mode.md index e307a9db..6edcdb8a 100644 --- a/en/02_Developer_Guides/09_Security/04_Sudo_Mode.md +++ b/en/02_Developer_Guides/09_Security/04_Sudo_Mode.md @@ -6,11 +6,11 @@ icon: key # Sudo mode -Sudo mode represents a heightened level of permission in that you are more certain that the current user is actually the person whose account is logged in. This is performed by re-validating that the account's password is correct, and will then last for a certain amount of time (configurable) until it will be checked again. +Sudo mode represents a heightened level of permission in that you are more certain that the current CMS user is actually the person whose account is logged in. This is performed by re-validating that the account's password is correct, and will then last for a certain amount of time (configurable) until it will be checked again. -Sudo mode will automatically be enabled for the configured lifetime when a user logs into the CMS. Note that if the PHP session lifetime expires before the sudo mode lifetime, that sudo mode will also be cleared (and re-enabled when the user logs in again). If the user leaves their CMS open, or continues to use it, for an extended period of time with automatic refreshing in the background, sudo mode will eventually deactivate once the max lifetime is reached. +Sudo mode is not active when a user logs in to the CMS. If the PHP session lifetime expires before the sudo mode lifetime, that sudo mode will also be cleared. If the user leaves their CMS open, or continues to use it, for an extended period of time with automatic refreshing in the background, sudo mode will eventually deactivate once the max lifetime is reached. -## Configuring the lifetime +## Configuring the sudo mode lifetime The default [`SudoModeServiceInterface`](api:SilverStripe\Security\SudoMode\SudoModeServiceInterface) implementation is [`SudoModeService`](api:SilverStripe\Security\SudoMode\SudoModeService), and its lifetime can be configured with YAML. You should read the lifetime value using `SudoModeServiceInterface::getLifetime()`. @@ -19,39 +19,61 @@ SilverStripe\Security\SudoMode\SudoModeService: lifetime_minutes: 25 ``` -## Enabling sudo mode for controllers +## Sudo mode for models -You can add the `SudoModeServiceInterface` singleton as a dependency to a controller that requires sudo mode for one of its actions: +Models which are protected by sudo mode (usually a [`DataObject`](api:SilverStripe\ORM\DataObject) subclass) are still viewable without entering the password. Any actions that modify data, e.g. POSTing an edit form submission, will require the user to enter their password. This is done to balance usability with security. -```php -namespace App\Control; +The following `DataObject` subclasses are protected by sudo mode out of the box: -class MyController extends Controller -{ - private ?SudoModeServiceInterface $sudoModeService = null; +- [`Member`](api:SilverStripe\Security\Member) +- [`Group`](api:SilverStripe\Security\Group) +- [`PermissionRole`](api:SilverStripe\Security\PermissionRole) +- [`PermissionRoleCode`](api:SilverStripe\Security\PermissionRoleCode) - private static array $dependencies = ['SudoModeService' => '%$' . SudoModeServiceInterface::class]; +### Configuring sudo mode for your models - public function setSudoModeService(SudoModeServiceInterface $sudoModeService): static - { - $this->sudoModeService = $sudoModeService; - return $this; - } +To add sudo mode for a particular model, including your `DataObject` subclasses, simply set the [`require_sudo_mode`](api:SilverStripe\View\ViewableData->require_sudo_mode) configuration property to `true`, either directly on the class or via yml. + +> [!NOTE] +> This will only add sudo mode to edit forms within the CMS interface. It will have no effect on forms outside of the CMS, such as custom forms in the frontend. + +```php +namespace App\Model; + +use SilverStripe\ORM\DataObject; + +class Product extends DataObject +{ + // ... + private static bool $require_sudo_mode = true; } ``` -Performing a sudo mode verification check in a controller action is simply using the service to validate the request: +```yml +SomeModule\Model\Player: + require_sudo_mode: true +``` + +> [!WARNING] If you add new fields to form that is protected by sudo mode, such as in an overridden [`LeftAndMain::getEditForm()`](api:SilverStripe\Admin\LeftAndMain::getEditForm()) method, it may mean having to call [`Form::requireSudoMode()`](api:SilverStripe\Forms\Form::requireSudoMode()) on the form to ensure the newly added fields are set to read-only while sudo mode is inactive. + +## Sudo mode for controller endpoints + +Performing a sudo mode verification check in a controller endpoint by using the sudo mode service to validate the request: ```php namespace App\Control; +use SilverStripe\Core\Injector\Injector; +use SilverStripe\Security\SudoMode\SudoModeServiceInterface; + class MyController extends Controller { // ... public function myAction(HTTPRequest $request): HTTPResponse { - if (!$this->sudoModeService->check($request->getSession())) { + $service = Injector::inst()->get(SudoModeServiceInterface::class); + if (!$service->check($request->getSession())) { return $this->httpError(403, 'Sudo mode is required for this action'); } // ... continue with sensitive operations @@ -59,14 +81,20 @@ class MyController extends Controller } ``` -## Using sudo mode in a react component +## Sudo mode for react components The `silverstripe/admin` module defines a [React Higher-Order-Component](https://reactjs.org/docs/higher-order-components.html) (aka HOC) which can be applied to React components in your module or code to intercept component rendering and show a "sudo mode required" information and log in screen, which will validate, activate sudo mode, and re-render the wrapped component afterwards on success. +This sudo mode differs from sudo mode for `DataObject`s in that it is not tied to a specific model, but rather to a +specific react component. + > [!WARNING] +> Sudo mode for react components does not protect the data the component manages, or any endpoints the component uses, it simply requires the user to re-enter their password before the component is rendered. + +> [!IMPORTANT] > The `WithSudoMode` HOC is exposed via [Webpack's expose-loader plugin](https://webpack.js.org/loaders/expose-loader/). You will need to add it as a [webpack external](https://webpack.js.org/configuration/externals/) to use it. The recommended way to do this is via the [@silverstripe/webpack-config npm package](https://www.npmjs.com/package/@silverstripe/webpack-config) which handles all the external configuration for you. You can get the injector to apply the HOC to your component automatically using [injector transformations](/developer_guides/customising_the_admin_interface/reactjs_redux_and_graphql/#transforming-services-using-middleware): diff --git a/en/08_Changelogs/5.4.0.md b/en/08_Changelogs/5.4.0.md index 1acaacee..92ee2aeb 100644 --- a/en/08_Changelogs/5.4.0.md +++ b/en/08_Changelogs/5.4.0.md @@ -8,6 +8,7 @@ title: 5.4.0 (unreleased) - [Security considerations](#security-considerations) - [Features and enhancements](#features-and-enhancements) + - [Sudo mode for sensitive data](#form-sudo-mode) - [Logged warning if allowed hosts have not been configured](#allowed-hosts-warning) - [New `XssSanitiser` class](#new-xsssanitiser-class) - [Option to change `ClassName` column from enum to varchar](#classname-varchar) @@ -33,6 +34,35 @@ We have provided a severity rating of the vulnerabilities below based on the CVS ## Features and enhancements +### Sudo mode for sensitive data {#form-sudo-mode} + +Some data managed by the CMS is always considered sensitive from a security point of view, such as member data and permissions assigned to groups. Data of this nature is now protected by default by "sudo mode" in the CMS. When a user tries to edit sensitive data, they will be prompted to enter their password to confirm their identity. + +This change was made to provide an extra layer of protection against cross site scripting (XSS) attacks, as well as people maliciously using someone elses computer left unattended in a logged-in state. + +Users will still be able to view sensitive data without entering their password as they could before. Previously "sudo mode" only protected a member's MFA settings. Now the following [`DataObject`](api:SilverStripe\ORM\DataObject) subclasses are also protected by sudo mode: + +- [`Member`](api:SilverStripe\Security\Member) +- [`Group`](api:SilverStripe\Security\Group) +- [`PermissionRole`](api:SilverStripe\Security\PermissionRole) +- [`PermissionRoleCode`](api:SilverStripe\Security\PermissionRoleCode) +- [`SiteConfig`](api:SilverStripe\SiteConfig\SiteConfig) + +You can also add sudo mode to your own `DataObject` subclass by setting the [`DataObject.require_sudo_mode`](api:SilverStripe\ORM\DataObject->require_sudo_mode) configuration property to `true`. For example: + +```yml +SomeModule\Model\Player: + require_sudo_mode: true +``` + +Previously sudo mode was automatically activated when a user logged in, which largely negated the value the feature provided. This has now been changed so that users will always have to enter their password to activate sudo mode the first time they reach a sensitive area of the CMS. + +There is still a configurable grace period where, after entering your password to activate sudo mode, it will remain active for a short time. + +If you run end-to-end tests on the CMS which involve editing sensitive data you may need to update your tests to account for this change. If you use behat there are [instructions](https://github.com/silverstripe/silverstripe-behat-extension/?tab=readme-ov-file#disabling-sudo-mode) for how to use an extension to automatically activate sudo mode in a feature file. + +[Learn more about sudo mode](/developer_guides/security/sudo_mode) in the developer docs. + ### Logged warning if allowed hosts have not been configured {#allowed-hosts-warning} If your site does not have one of the following configured, then a warning will now be logged on every request: