From 8d2a1ba8be68b1bda6cf9f1b046ac9d3d1323087 Mon Sep 17 00:00:00 2001 From: Simon Gow Date: Fri, 7 Apr 2017 11:27:07 +1200 Subject: [PATCH 1/2] Index documentation - updating index documentation to give a better description of how to improve performance with silverstripe applications --- .../00_Model/12_Indexes.md | 56 ++++++++++++++++--- 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md index a3e2c2e72..d09d12983 100644 --- a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md +++ b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md @@ -2,10 +2,26 @@ title: Indexes summary: Add Indexes to your Data Model to optimize database queries. # Indexes +Indexes are a great way to improve performance in your application, especially as it grows. By adding indexes to your +data model you can reduce the time taken for the framework to find and filter data objects. -It is sometimes desirable to add indexes to your data model, whether to optimize queries or add a uniqueness constraint -to a field. This is done through the `DataObject::$indexes` map, which maps index names to descriptor arrays that -represent each index. There're several supported notations: +The addition of an indexes should be carefully evaluated as they can also increase the cost of other operations such as +`UPDATE`/`INSERT` and `DELETE`. An index which has the same cardinality as the table will actually cost you performance. + +It's important to find the right balance to achieve fast queries using the optimal set of indexes; For SilverStripe +applications it's a good practice to: +- add indexes on columns which are frequently used in `filter`, `where` or `orderBy` statements +- for these, only include indexes for columns which are the most restrictive (return the least number of rows) + +The SilverStripe framework already places certain indexes for you by default: +- The primary key for each model has a `PRIMARY KEY` unique index +- The `ClassName` column if your model is a direct decedent from `DataObject` +- All relationships defined in the model have indexes for their `has_one` entity (for `many_many` relationships +this index is present on the associative entity). + +## Defining an index +Indexes are represented on a data object through the `DataObject::$indexes` array which maps index names to a +descriptor. There are several supported notations: :::php ' => true, - '' => array('type' => '', 'value' => '""'), '' => 'unique("")' + '' => array( + 'type' => '', + 'value' => '""' + ), ); } - + +The `` is used to put a standard non-unique index on the column specified. For complex or large tables +we recommend building the index to suite the requirements of your data. + The `` can be an arbitrary identifier in order to allow for more than one index on a specific database column. The "advanced" notation supports more `` notations. These vary between database drivers, but all of them support the following: - * `index`: Standard index + * `index`: Standard non unique index. * `unique`: Index plus uniqueness constraint on the value * `fulltext`: Fulltext content index -In order to use more database specific or complex index notations, we also support raw SQL as a value in the -`$indexes` definition. Keep in mind that using raw SQL is likely to make your code less portable between DBMSs. - **mysite/code/MyTestObject.php** :::php @@ -50,6 +69,25 @@ In order to use more database specific or complex index notations, we also suppo ); } +## Complex/Composite Indexes +For complex queries it may be necessary to define a complex or composite index on the supporting object. To create a +composite index, define the fields in the index order as a comma separated list. + +*Note* Most DBMSs only use the leftmost prefix to optimise the query, try to ensure the order of the index and your +query parameters are the same. e.g. +- index (col1) - `WHERE col1 = ?` +- index (col1, col2) = `WHERE (col1 = ? AND col2 = ?)` +- index (col1, col2, col3) = `WHERE (col1 = ? AND col2 = ? AND col3 = ?)` + +The index would not be used for a query `WHERE col2 = ?` or for `WHERE col1 = ? OR col2 = ?` + +As an alternative to a composite index, you can also create a hashed column which is a combination of information from +other columns. If this is indexed, smaller and reasonably unique it might be faster that an index on the whole column. + +## Index Creation/Destruction +Indexes are generated and removed automatically during a `dev/build`. Caution if you're working with large tables and +modify an index as the next `dev/build` will `DROP` the index, and then `ADD` it. + ## API Documentation * [api:DataObject] From 5f82997690ebbe73f5241168db8e55e181bdaf94 Mon Sep 17 00:00:00 2001 From: Simon Gow Date: Mon, 10 Apr 2017 15:42:17 +1200 Subject: [PATCH 2/2] Secure Coding - Security Headers, Force HTTPS and Cookies - Amending best practices for secure coding to enforce HTTPS - Add security headers to enforce HTTPS - Ensure secure cookies are used. - Added links for testing, changed documentation as part of peer review. - Arrange headers to work with HTTP interface. - fixed Cache-Control case - Added reference to Secure Sessions. - Replaced Cardinality with unique - Fixed innacurate reference to decendant. - Consistent spelling - Databases over DBMSs --- .../00_Model/12_Indexes.md | 11 +-- .../09_Security/04_Secure_Coding.md | 84 ++++++++++++++++++- 2 files changed, 88 insertions(+), 7 deletions(-) diff --git a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md index d09d12983..783577f8c 100644 --- a/docs/en/02_Developer_Guides/00_Model/12_Indexes.md +++ b/docs/en/02_Developer_Guides/00_Model/12_Indexes.md @@ -6,7 +6,8 @@ Indexes are a great way to improve performance in your application, especially a data model you can reduce the time taken for the framework to find and filter data objects. The addition of an indexes should be carefully evaluated as they can also increase the cost of other operations such as -`UPDATE`/`INSERT` and `DELETE`. An index which has the same cardinality as the table will actually cost you performance. +`UPDATE`/`INSERT` and `DELETE`. An index on a column whose data is non unique will actually cost you performance. +E.g. In most cases an index on `boolean` status flag, or `ENUM` state will not increase query performance. It's important to find the right balance to achieve fast queries using the optimal set of indexes; For SilverStripe applications it's a good practice to: @@ -15,12 +16,12 @@ applications it's a good practice to: The SilverStripe framework already places certain indexes for you by default: - The primary key for each model has a `PRIMARY KEY` unique index -- The `ClassName` column if your model is a direct decedent from `DataObject` +- The `ClassName` column if your model inherits from `DataObject` - All relationships defined in the model have indexes for their `has_one` entity (for `many_many` relationships this index is present on the associative entity). ## Defining an index -Indexes are represented on a data object through the `DataObject::$indexes` array which maps index names to a +Indexes are represented on a `DataObject` through the `DataObject::$indexes` array which maps index names to a descriptor. There are several supported notations: :::php @@ -40,7 +41,7 @@ descriptor. There are several supported notations: The `` is used to put a standard non-unique index on the column specified. For complex or large tables we recommend building the index to suite the requirements of your data. - + The `` can be an arbitrary identifier in order to allow for more than one index on a specific database column. The "advanced" notation supports more `` notations. These vary between database drivers, but all of them support the following: @@ -73,7 +74,7 @@ support the following: For complex queries it may be necessary to define a complex or composite index on the supporting object. To create a composite index, define the fields in the index order as a comma separated list. -*Note* Most DBMSs only use the leftmost prefix to optimise the query, try to ensure the order of the index and your +*Note* Most databases only use the leftmost prefix to optimise the query, try to ensure the order of the index and your query parameters are the same. e.g. - index (col1) - `WHERE col1 = ?` - index (col1, col2) = `WHERE (col1 = ? AND col2 = ?)` diff --git a/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md b/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md index 9dc949973..0fad9f2b4 100644 --- a/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md +++ b/docs/en/02_Developer_Guides/09_Security/04_Secure_Coding.md @@ -426,8 +426,6 @@ Note that there is also a 'SilverStripe' way of casting fields on a class, this standard PHP way. See [casting](/developer_guides/model/data_types_and_casting). - - ## Filesystem ### Don't script-execution in /assets @@ -585,6 +583,88 @@ In a future release this behaviour will be changed to be on by default, and this variable will be no longer necessary, thus it will be necessary to always set `SS_TRUSTED_PROXY_IPS` if using a proxy. +## Secure Sessions, Cookies and TLS (HTTPS) + +SilverStripe recommends the use of TLS(HTTPS) for your application, and you can easily force the use through the +director function `forceSSL()` + + :::php + + if (!Director::isDev()) { + Director::forceSSL(); + } + +Forcing HTTPS so requires a certificate to be purchased or obtained through a vendor such as +[lets encrypt](https://letsencrypt.org/) and configured on your web server. + +We also want to ensure cookies are not shared between secure and non-secure sessions, so we must tell SilverStripe to +use a [secure session](https://docs.silverstripe.org/en/3/developer_guides/cookies_and_sessions/sessions/#secure-session-cookie). +To do this, you may set the `cookie_secure` parameter to `true` in your `config.yml` for `Session` + + :::yml + Session: + cookie_secure: true + +For other cookies set by your application we should also ensure the users are provided with secure cookies by setting +the "Secure" and "HTTPOnly" flags. These flags prevent them from being stolen by an attacker through javascript. + + - The `Secure` cookie flag instructs the browser not to send the cookie over an insecure HTTP connection. If this +flag is not present, the browser will send the cookie even if HTTPS is not in use, which means it is transmitted in +clear text and can be intercepted and stolen by an attacker who is listening on the network. + +- The `HTTPOnly` flag lets the browser know whether or not a cookie should be accessible by client-side JavaScript +code. It is best practice to set this flag unless the application is known to use JavaScript to access these cookies +as this prevents an attacker who achieves cross-site scripting from accessing these cookies. + + + :::php + + Cookie::set('cookie-name', 'chocolate-chip', $expiry = 30, $path = null, $domain = null, $secure = true, + $httpOnly = false + ); + +## Security Headers + +In addition to forcing HTTPS browsers can support additional security headers which can only allow access to a website +via a secure connection. As browsers increasingly provide negative feedback regarding unencrypted HTTP connections, +ensuring an HTTPS connection will provide a better and more secure user experience. + +- The `Strict-Transport-Security` header instructs the browser to record that the website and assets on that website +MUST use a secure connection. This prevents websites from becoming insecure in the future from stray absolute links +or references without https from external sites. Check if your browser supports [HSTS](https://hsts.badssl.com/) + - `max-age` can be configured to anything in seconds: `max-age=31536000` (1 year), for roll out, consider something + lower + - `includeSubDomains` to ensure all present and future sub domains will also be HTTPS + +For sensitive pages, such as members areas, or places where sensitive information is present, adding cache control + headers can explicitly instruct browsers not to keep a local cached copy of content and can prevent content from + being cached throughout the infrastructure (e.g. Proxy, caching layers, WAF etc). + +- The headers `Cache-control: no-store` and `Pragma: no-cache` along with expiry headers of `Expires: ` +and `Date: ` will ensure that sensitive content is not stored locally or able to be retrieved by +unauthorised local persons. SilverStripe adds the current date for every request, and we can add the other cache + headers to the request for our secure controllers: + + + :::php + + class MySecureController extends Controller { + + public function init() { + parent::init(); + + // Add cache headers to ensure sensitive content isn't cached. + $this->response->addHeader('Cache-Control', 'max-age=0, must-revalidate, no-transform'); + $this->response->addHeader('Pragma', 'no-cache'); // for HTTP 1.0 support + + HTTP::set_cache_age(0); + HTTP::add_cache_headers($this->response); + + // Add HSTS header to force TLS for document content + $this->response->addHeader('Strict-Transport-Security', 'max-age=86400; includeSubDomains'); + } + } + ## Related * [http://silverstripe.org/security-releases/](http://silverstripe.org/security-releases/)