diff --git a/en/02_Developer_Guides/00_Model/09_Validation.md b/en/02_Developer_Guides/00_Model/09_Validation.md index 57ce8d52..19327651 100644 --- a/en/02_Developer_Guides/00_Model/09_Validation.md +++ b/en/02_Developer_Guides/00_Model/09_Validation.md @@ -44,6 +44,9 @@ sub-classes to specify custom validation, or use the `updateValidate()` extensio Invalid objects won't be able to be written - a [`ValidationException`](api:SilverStripe\Core\Validation\ValidationException) will be thrown and no write will occur. +If appropriate, you can call [`ValidationResult::setModelClass()`](api:SilverStripe\Core\Validation\ValidationResult::setModelClass()) +and [`ValidationResult::setRecordID()`](api:SilverStripe\Core\Validation\ValidationResult::setRecordID()) on your `ValidationResult` instance to set the class and ID of the object being validated. This additional info will show in the validation error messages in a CLI context, as well as in a non-CLI context if the current controller is an instance or a subclass of a controller configured in the [`ValidationException.show_additional_info_non_cli_controllers`](api:SilverStripe\Core\Validation\ValidationException->show_additional_info_non_cli_controllers) configuration. + The return value of `validate()` is a [`ValidationResult`](api:SilverStripe\Core\Validation\ValidationResult) object. ```php diff --git a/en/02_Developer_Guides/03_Forms/00_Introduction.md b/en/02_Developer_Guides/03_Forms/00_Introduction.md index 5270b8c0..f887801c 100644 --- a/en/02_Developer_Guides/03_Forms/00_Introduction.md +++ b/en/02_Developer_Guides/03_Forms/00_Introduction.md @@ -388,7 +388,7 @@ class MyFormPageController extends PageController echo $data['Email']; // You can also fetch the value from the field. - echo $form->Fields()->dataFieldByName('Email')->Value(); + echo $form->Fields()->dataFieldByName('Email')->getValue(); // Using the Form instance you can get / set status such as error messages. $form->sessionMessage('Successful!', 'good'); diff --git a/en/02_Developer_Guides/03_Forms/01_Validation.md b/en/02_Developer_Guides/03_Forms/01_Validation.md index a348a8e3..04d3c65d 100644 --- a/en/02_Developer_Guides/03_Forms/01_Validation.md +++ b/en/02_Developer_Guides/03_Forms/01_Validation.md @@ -90,7 +90,7 @@ class FormFieldValidationExtension extends Extension { protected function updateValidate(ValidationResult $result): void { - if (str_ends_with($this->owner->Value(), '@example.com')) { + if (str_ends_with($this->owner->getValue(), '@example.com')) { $result->addFieldError( $this->getOwner()->Name(), 'Please provide a valid email address which does not end with @example.com' @@ -118,7 +118,7 @@ class CustomNumberField extends NumericField public function validate(): ValidationResult { $this->beforeExtending('updateValidate', function (ValidationResult $result) { - if ((int) $this->Value() === 20) { + if ((int) $this->getValue() === 20) { $result->addFieldError($this->Name(), 'This value cannot be 20'); } }); diff --git a/en/02_Developer_Guides/09_Security/05_Secure_Coding.md b/en/02_Developer_Guides/09_Security/05_Secure_Coding.md index 3f03c57f..eea15ad2 100644 --- a/en/02_Developer_Guides/09_Security/05_Secure_Coding.md +++ b/en/02_Developer_Guides/09_Security/05_Secure_Coding.md @@ -144,7 +144,7 @@ class MyForm extends Form } ``` -- `FormField->Value()` +- `FormField->getValue()` - URLParams passed to a Controller-method Example: @@ -673,45 +673,106 @@ This is a recommended option to secure any controller which displays or submits sensitive user input, and is enabled by default in all CMS controllers, as well as the login form. -## Request hostname forgery {#request-hostname-forgery} +## Request hostname forgery and host header injection attacks {#request-hostname-forgery} -To prevent a forged hostname appearing being used by the application, Silverstripe CMS -allows the configure of a whitelist of hosts that are allowed to access the system. By defining -this whitelist in your `.env` file, any request presenting a `Host` header that is -*not* in this list will be blocked with a HTTP 400 error: +Ideally your hosting will reject invalid host headers. For example Apache allows you to define valid hosts as part of the virtual host configuration, and a Web Application Firewall (WAF) can also be configured to validate the host header. However if your hosting is set to allow *any* host header, your project might be vulnerable to host header injection attacks. + +To prevent a forged hostname being used by your application, Silverstripe CMS +allows the configuration of an allow list of valid hosts that are allowed in the `Host` header. This is an extra layer of protection against this type of attack. +By defining this allow list, any request presenting a `Host` header that is *not* in this list will be blocked with a HTTP 400 error. + +While you should have appropriate validation at a hosting level, it is best practice to also configure this in your project. + +> [!NOTE] +> If this configuration is not set, a warning will be logged. +> If you have implemented adequate protections in your hosting already and have legitimate reasons to not set this configuration, +> you can explicitly set it to `"*"`. That will disable logging and declare that your project should explicitly allow any host +> header without validating it. + +The main way to configure this is using the `SS_ALLOWED_HOSTS` environment variable: ```bash -SS_ALLOWED_HOSTS="www.example.com,example.com,subdomain.example.com" +SS_ALLOWED_HOSTS="example.com,www.example.com,subdomain.example.com" +``` + +You can also configure the allow list with YAML configuration, which can be useful if you don't have full control over your hosting: + +```yml +--- +after: requestprocessors +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\Control\Middleware\AllowedHostsMiddleware: + properties: + AllowedHosts: + - 'example.com' + - 'www.example.com' + - 'subdomain.example.com' ``` -Please note that if this configuration is defined, you *must* include *all* subdomains (eg `.`) -that will be accessing the site. +> [!WARNING] +> Please note that you *must* include *all* subdomains (eg `www.`) that will be serving out content from the project using the same codebase. +> For example if your CMS serves content on the following hosts, *all* of them must be added to the allow list: +> +> - `example.com` (primary domain) +> - `www.example.com` (www subdomain) +> - `blog.example.com` (subdomain for a blog) +> - `example.org` (secondary domain serving the same content) + +Note that domains which only *redirect* to your project (i.e. with a `301` HTTP response) should not be added as allowed hosts. + +### Reverse proxies and forwarded hosts When Silverstripe CMS is run behind a reverse proxy, it's normally necessary for this proxy to use the `X-Forwarded-Host` request header to tell the webserver which hostname was originally -requested. However, when Silverstripe CMS is not run behind a proxy, this header can still be +requested. The [`TrustedProxyMiddleware`](api:SilverStripe\Control\Middleware\TrustedProxyMiddleware) ensures only trusted IP addresses are allowed to use +that header to override the host header in Silverstripe CMS. + +Without that middleware, the `X-Forwarded-Host` header could be used by attackers to fool the server into mistaking its own identity. The risk of this kind of attack causing damage is especially high on sites which utilise caching mechanisms, as rewritten urls could persist between requests in order to misdirect other users into visiting external sites. -In order to prevent this kind of attack, it's necessary to whitelist trusted proxy -server IPs using the SS_TRUSTED_PROXY_IPS define in your `.env`. +In order to prevent this kind of attack, if your project is behind a reverse proxy, it's necessary to define the trusted proxy +server IP addresses in an allow list. + +The primary way to define this allow list is using the `SS_TRUSTED_PROXY_IPS` environment variable: ```bash SS_TRUSTED_PROXY_IPS="127.0.0.1,192.168.0.1" ``` -You can also whitelist subnets in CIDR notation if you don't know the exact IP of a trusted proxy. -For example, some cloud provider load balancers don't have fixed IPs. +You can also allow subnets in CIDR notation if you don't know the exact IP of a trusted proxy. +For example, some cloud provider load balancers don't have fixed IP addresses. ```bash SS_TRUSTED_PROXY_IPS="10.10.0.0/24,10.10.1.0/24,10.10.2.0/24" ``` +You can also define the allow list using YAML configuration: + +```yml +--- +after: requestprocessors +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\Control\Middleware\TrustedProxyMiddleware: + properties: + TrustedProxyIPs: + - '127.0.0.1' + - '192.168.0.1' +``` + +If there is no proxy server, 'none' can be used to explicitly distrust all clients. +If only trusted servers will make requests then you can use '*' to trust all clients. +Otherwise a comma separated list of individual IP addresses (or subnets in CIDR notation) should be declared. + +At the same time, you'll also need to define which headers you trust from these proxy IPs. Since there are multiple ways through which proxies can pass through HTTP information on the original hostname, IP and protocol, these values need to be adjusted for your specific proxy. The header names match their equivalent `$_SERVER` values. + If you wish to change the headers that are used to find the proxy information, you should reconfigure the -TrustedProxyMiddleware service: +`TrustedProxyMiddleware` service: ```yml SilverStripe\Control\TrustedProxyMiddleware: @@ -721,58 +782,47 @@ SilverStripe\Control\TrustedProxyMiddleware: ProxyIPHeaders: X-Forwarded-Ip ``` -```bash -SS_TRUSTED_PROXY_HOST_HEADER="HTTP_X_FORWARDED_HOST" -SS_TRUSTED_PROXY_IP_HEADER="HTTP_X_FORWARDED_FOR" -SS_TRUSTED_PROXY_PROTOCOL_HEADER="HTTP_X_FORWARDED_PROTOCOL" -``` - -At the same time, you'll also need to define which headers you trust from these proxy IPs. Since there are multiple ways through which proxies can pass through HTTP information on the original hostname, IP and protocol, these values need to be adjusted for your specific proxy. The header names match their equivalent `$_SERVER` values. - -If there is no proxy server, 'none' can be used to distrust all clients. -If only trusted servers will make requests then you can use '*' to trust all clients. -Otherwise a comma separated list of individual IP addresses (or subnets in CIDR notation) should be declared. +## TLS (aka SSL aka HTTPS) -This behaviour is enabled whenever `SS_TRUSTED_PROXY_IPS` is defined, or if the -`BlockUntrustedIPs` environment variable is declared. It is advisable to include the -following in your .htaccess to ensure this behaviour is activated. +Silverstripe CMS recommends the use of TLS (HTTPS) for your application. You can configure this by setting the `ForceSSL` property on the [`CanonicalURLMiddleware`](api:SilverStripe\Control\Middleware\CanonicalURLMiddleware) singleton. -```text - - # Ensure that X-Forwarded-Host is only allowed to determine the request - # hostname for servers ips defined by SS_TRUSTED_PROXY_IPS in your .env - # Note that in a future release this setting will be always on. - SetEnv BlockUntrustedIPs true - +```yml +--- +After: '#canonicalurls' +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\Control\Middleware\CanonicalURLMiddleware: + properties: + ForceSSL: true ``` -This behaviour is on by default; the environment variable is not required. For correct operation, it is necessary to always set `SS_TRUSTED_PROXY_IPS` if using a proxy. - -## Secure sessions, cookies and TLS (HTTPS) +This will only take effect in environment types that `CanonicalURLMiddleware` is configured to apply to (by +default all environments). To apply this behaviour to only specific environment types, you'll need to either change +what environments this is enabled for, or tailor the configuration to the active environment. -Silverstripe CMS recommends the use of TLS (HTTPS) for your application, and you can easily force the use through the -director function `forceSSL()` +> [!WARNING] +> Note that setting `EnabledEnvs` will affect more than just the "force SSL" behaviour. ```php -use SilverStripe\Control\Director; +use SilverStripe\Control\Middleware\CanonicalURLMiddleware; +use SilverStripe\Core\Kernel; -if (!Director::isDev()) { - Director::forceSSL(); -} +CanonicalURLMiddleware::singleton()->setEnabledEnvs([ + Kernel::TEST, + Kernel::LIVE, +]); ``` -`forceSSL()` will only take effect in environment types that `CanonicalURLMiddleware` is configured to apply to (by -default, only `LIVE`). To apply this behaviour in all environment types, you'll need to update that configuration: - -```php -use SilverStripe\Control\Director; -use SilverStripe\Control\Middleware\CanonicalURLMiddleware; - -if (!Director::isDev()) { - // You can also specify individual environment types - CanonicalURLMiddleware::singleton()->setEnabledEnvs(true); - Director::forceSSL(); -} +```yml +--- +Only: + environment: 'dev' +After: '#canonicalurls' +--- +SilverStripe\Core\Injector\Injector: + SilverStripe\Control\Middleware\CanonicalURLMiddleware: + properties: + ForceSSL: false ``` Forcing HTTPS so requires a certificate to be purchased or obtained through a vendor such as @@ -780,8 +830,21 @@ Forcing HTTPS so requires a certificate to be purchased or obtained through a ve Note that by default enabling SSL will also enable `CanonicalURLMiddleware::forceBasicAuthToSSL` which will detect and automatically redirect any requests with basic authentication headers to first be served over HTTPS. You can -disable this behaviour using `CanonicalURLMiddleware::singleton()->setForceBasicAuthToSSL(false)`, or via Injector -configuration in YAML. +disable this behaviour setting the `ForceBasicAuthToSSL` property to `false` in the YAML configuration. + +### Using SSL in database connections + +In some circumstances, like connecting to a database on a remote host for example, you may wish to enable SSL encryption to ensure the protection of sensitive information and database access credentials. +You can configure that by setting the following environment variables: + +| Name | Description | +| ---- | ----------- | +| `SS_DATABASE_SSL_KEY` | Absolute path to SSL key file (optional - but if set, `SS_DATABASE_SSL_CERT` must also be set) | +| `SS_DATABASE_SSL_CERT` | Absolute path to SSL certificate file (optional - but if set, `SS_DATABASE_SSL_KEY` must also be set) | +| `SS_DATABASE_SSL_CA` | Absolute path to SSL Certificate Authority bundle file (optional) | +| `SS_DATABASE_SSL_CIPHER` | Custom SSL cipher for database connections (optional) | + +## Secure sessions and cookies We also want to ensure cookies are not shared between secure and non-secure sessions, so we must tell Silverstripe CMS to use a [secure session](/developer_guides/cookies_and_sessions/sessions/#secure-session-cookie). @@ -839,18 +902,6 @@ Cookie::set( ); ``` -### Using SSL in database connections - -In some circumstances, like connecting to a database on a remote host for example, you may wish to enable SSL encryption to ensure the protection of sensitive information and database access credentials. -You can configure that by setting the following environment variables: - -| Name | Description | -| ---- | ----------- | -| `SS_DATABASE_SSL_KEY` | Absolute path to SSL key file (optional - but if set, `SS_DATABASE_SSL_CERT` must also be set) | -| `SS_DATABASE_SSL_CERT` | Absolute path to SSL certificate file (optional - but if set, `SS_DATABASE_SSL_KEY` must also be set) | -| `SS_DATABASE_SSL_CA` | Absolute path to SSL Certificate Authority bundle file (optional) | -| `SS_DATABASE_SSL_CIPHER` | Custom SSL cipher for database connections (optional) | - ## Security headers In addition to forcing HTTPS browsers can support additional security headers which can only allow access to a website diff --git a/en/02_Developer_Guides/15_Customising_the_Admin_Interface/06_Preview.md b/en/02_Developer_Guides/15_Customising_the_Admin_Interface/06_Preview.md index 728bad92..896fe854 100644 --- a/en/02_Developer_Guides/15_Customising_the_Admin_Interface/06_Preview.md +++ b/en/02_Developer_Guides/15_Customising_the_Admin_Interface/06_Preview.md @@ -436,12 +436,12 @@ class ProductPage extends Page public function MetaTags($includeTitle = true) { $tags = parent::MetaTags($includeTitle); - if (!Controller::has_curr()) { + $controller = Controller::curr(); + if ($controller === null) { return; } // If the 'DataObjectPreview' GET parameter is present, remove 'x-page-id' and 'x-cms-edit-link' meta tags. // This ensures that toggling between draft/published states doesn't revert the CMS to the page's edit form. - $controller = Controller::curr(); $request = $controller->getRequest(); if ($request->getVar('DataObjectPreview') !== null) { $html = HTMLValue::create($tags); diff --git a/en/02_Developer_Guides/18_Cookies_And_Sessions/01_Cookies.md b/en/02_Developer_Guides/18_Cookies_And_Sessions/01_Cookies.md index 6a09f18f..d96d39c4 100644 --- a/en/02_Developer_Guides/18_Cookies_And_Sessions/01_Cookies.md +++ b/en/02_Developer_Guides/18_Cookies_And_Sessions/01_Cookies.md @@ -7,7 +7,7 @@ icon: cookie-bite # Cookies Note that cookies can have security implications - before setting your own cookies, make sure to read through the -[secure coding](/developer_guides/security/secure_coding#secure-sessions-cookies-and-tls-https) documentation. +[secure coding](/developer_guides/security/secure_coding#secure-sessions-and-cookies) documentation. ## Accessing and manipulating cookies diff --git a/en/08_Changelogs/6.0.0.md b/en/08_Changelogs/6.0.0.md index 966d70e9..a2141fe7 100644 --- a/en/08_Changelogs/6.0.0.md +++ b/en/08_Changelogs/6.0.0.md @@ -37,6 +37,8 @@ title: 6.0.0 (unreleased) - [`FormField` classes now use `FieldValidator` for validation](#formfield-validation) - [Most extension hook methods are now protected](#hooks-protected) - [Changes to some extension hook names](#hooks-renamed) + - [`FormField::Value()` split into two methods](#formfield-value) + - [`Controller::has_curr()` removed](#controller-has-curr) - [Strict typing for `Factory` implementations](#factory-strict-typing) - [Elemental `TopPage` class names changed](#elemental-top-page) - [List interface changes](#list-interface-changes) @@ -50,6 +52,7 @@ title: 6.0.0 (unreleased) - [Update JS MIME type, remove `type` in `