Problem/Motivation

Third party implementations should be able to modify session options and therefore DrupalKernel::initializeCookieGlobals() needs to be replaced by a service. Regrettably #2331909: Move DrupalKernel::initializeCookieGlobals() into page cache kernel decorator encountered major road blocks, therefore let's try a different approach.

A couple of PHP ini-settings need to be set up correctly in order to ensure that sessions are safe and work seamlessly. In Drupal most of those values were enforced from within settings.php. However, the value of some of those ini-settings depend on a request object with reverse proxy headers already validated. It follows that those values cannot be set statically in a config file or as a container parameter but need to be determined at runtime. This applies for the session_name (in order to properly keep apart secure (HTTPS-only) session cookies from plain ones), but also for the cookie_domain.

The NativeSessionStorage class (the parent class of the Drupal SessionManager), accepts an array of ini-options as a constructor argument. It also can receive it through its setOptions method. However, Symfony does not support advanced use-cases like the ones outlined above out of the box.

Taking the best of both worlds, let's introduce the session.storage.options container parameter and augment those using a new session_configuration service just before a session is started.

It has been suggested elsewhere (#1934730-8: Alternative session handler implementation is not able to override session_name()) that the responsibility of setting the session name should be moved to the SessionManager. However, that would result in the session manager being instantiated including all of its dependencies when the page cache request policy desires to verify whether there is a session cookie on an incoming request. The session configuration service does not have any dependencies and therefore it is save to assume that its instantiation has less impact on the response time when pages are served from the cache.

Proposed resolution

Introduce a Drupal\Core\Session\SessionConfiguration service which is capable of choosing session ini-values on a per request basis. Sites and modules which need to modify session ini settings (like e.g. Secure Pages) may supply their own implementation of the session configuration service.

Note, this ensures that even though global $cookie_domain is replaced by a container parameter, it is still possible for the session configuration service to choose a different value for session.cookie_domain based on the request.

Remaining tasks

Review.

User interface changes

None.

API changes

  • Replace the global $cookie_domain by a container parameter.
  • Move session related ini settings from settings.php to a container parameter.

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Bug because it is resolving #1934730: Alternative session handler implementation is not able to override session_name()
Issue priority Major because it provides contrib with a clean way to implement mixed mode SSL support (Secure Pages). This resolves a temporary regression introduced by #2342593: Remove mixed SSL support from core.
Disruption Moderately disruptive for existing sites because they might need to replicate the changes to site settings.php, services.yml

Comments

znerol’s picture

znerol’s picture

Status: Active » Needs review

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
StatusFileSize
new20.93 KB
new5.1 KB
  • Reuse the trick from #2331909-27: Move DrupalKernel::initializeCookieGlobals() into page cache kernel decorator and use the database-prefix as the session name for the simpletest child site.
  • Remove the unprefixedName and cookieDomain from SessionConfiguration class, they are cheap to compute and they depend on the $request. Therefore the cache is technically incorrect.
  • Remove the custom code for sanitizing the HTTP host. The Symfony Request::getHost() method in fact throws an exception if the host-name contains unexpected characters as well as if it starts with a period. It also removes the port.
znerol’s picture

Uh, forgot to add session.cookie_secure...

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
znerol’s picture

Another option would be to use the a service configurator which consumes the request stack and then sets up the session manager appropriately. However, in that case the hasSession method would needs another new home.

znerol’s picture

This moves session configuration into container parameters and removes the $cookie_domain global variable.

znerol’s picture

Issue summary: View changes
znerol’s picture

Issue summary: View changes
znerol’s picture

StatusFileSize
new10.45 KB

Add unit tests and fix a little mistake in SessionConfiguration::getCookieDomain() detected while adding unit tests.

Crell’s picture

Most of this is out of my wheelhouse, so I cannot RTBC it. I didn't notice anything major from a read through, though. Just a few doc related comments below:

  1. +++ b/core/lib/Drupal/Core/Session/SessionConfiguration.php
    @@ -0,0 +1,193 @@
    +  /**
    +   * Constructs a new session configuration instance.
    +   *
    +   * @param array $options
    +   *   An associative array of session ini settings.
    +   *
    +   * @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::__construct()
    +   */
    

    Can we also add a @see to the PHP documentation on what those ini settings are?

  2. +++ b/core/lib/Drupal/Core/Session/SessionConfiguration.php
    @@ -0,0 +1,193 @@
    +    // Per RFC 2109, cookie domains must contain at least one dot other than the
    +    // first. For hosts such as 'localhost' or IP Addresses we don't set a
    +    // cookie domain.
    

    Please link to the RFC.

    Although according to Wikipedia that RFC has been obsoleted for a long time, and there are multiple more recent RFCs to reference instead:

    http://en.wikipedia.org/wiki/HTTP_cookie

  3. +++ b/sites/default/default.services.yml
    @@ -1,4 +1,40 @@
     parameters:
    +  session.storage.options:
    +    # Default ini options for sessions.
    +    #
    

    Ooo...

znerol’s picture

Regrettably cookie domain implementations are a mess (and also the RFCs partially contradicting each other etc.). Citing from a random blog entry:

  • When no domain is set in the cookie, the cookie should only match the exact host name of the request. No sub domains, no partial matches. This means simply not including the domain attribute – it is not valid to set an empty domain attribute. Unfortunately, Internet Explorer appears to treat this as the host name along with any subdomains.
  • When setting a domain in the cookie, the safe choice is to have it preceded by a dot, like .erik.io. The cookie will match with all sub domains.
  • Setting a cookie domain without a preceding dot, like erik.io, is invalid in RFC 2109 implementations, and will produce the same behaviour as with a preceding dot on other implementations. There is no way to restrict a cookie to a specific explicitly set domain, without sub domains being included.

Let's just specify that the leading dot is required to maximize compatibility and the embedded dot is required because user agents will hopefully never accept cookies for top-level domains.

znerol’s picture

StatusFileSize
new2.09 KB

Status: Needs review » Needs work
znerol’s picture

Status: Needs work » Needs review
znerol’s picture

klausi’s picture

Status: Needs review » Needs work

HTTPS mixed mode has been removed.

znerol’s picture

Status: Needs work » Needs review
StatusFileSize
new33.68 KB
new4.67 KB
dawehner’s picture

  1. +++ b/core/lib/Drupal/Core/Session/SessionConfiguration.php
    @@ -0,0 +1,161 @@
    +  protected function getCookieDomain(Request $request) {
    +    if (isset($this->options['cookie_domain'])) {
    

    The IS has to describe that we change global $cookie_domain; and converts it into a container parameter. I can't think of a usecase where this cookie domain has to be dynamic ... on top you probably rely on the host most of the time anyway.

  2. +++ b/core/lib/Drupal/Core/Session/SessionConfigurationInterface.php
    @@ -0,0 +1,38 @@
    +  /**
    +   * Determines whether this request is potentially associated with a session.
    +   *
    

    I wonder whether we can specify was potentially means.

  3. +++ b/core/tests/Drupal/Tests/Core/Session/SessionConfigurationTest.php
    @@ -0,0 +1,252 @@
    +class SessionConfigurationTest extends UnitTestCase {
    

    Impressive test coverage!

  4. +++ b/sites/default/default.services.yml
    @@ -1,4 +1,40 @@
    +    # between your various domains. Make sure to always start the $cookie_domain
    +    # with a leading dot, as per RFC 2109.
    

    seriously RFC!

znerol’s picture

Fixed the issue summary, added upgrade path tag, fixed the docs.

andypost’s picture

Looks great! +1 to RTBC

Crell’s picture

Only one minor nit:

+++ b/core/tests/Drupal/Tests/Core/PageCache/NoSessionOpenTest.php
@@ -19,11 +19,11 @@
-   * @var string
+   * @var \Drupal\Core\Session\SessionConfigurationInterface|\PHPUnit_Framework_MockObject_MockObject

I don't think we ever list the mock object as a var possibility, do we? If it's a mock that should be transparent, and conform to that interface anyway, no?

Otherwise I am comfortable here.

znerol’s picture

I don't think we ever list the mock object as a var possibility.

Maybe I'm misunderstanding the comment here, but we use @var \Drupal\Some\ClassInterface|\PHPUnit_Framework_MockObject_MockObject all over the place.

CR draft: https://www.drupal.org/node/2391871

Crell’s picture

Why in the name of Zork are we doing that...?

(If we are, then that shouldn't block this issue but it's nonsensical for us to be doing in the first place, IMO.)

znerol’s picture

I guess that's for folks with IDEs. When specifying both types in @var it is likely possible to autocomplete methods of PHPUnit_Framework_MockObject_MockObject (e.g. expects()) in addition to those on the original class.

Crell’s picture

Status: Needs review » Reviewed & tested by the community

*sigh* Durpal.

dawehner’s picture

Yeah, its a huge DX win for you, if you have documented the types.

Status: Reviewed & tested by the community » Needs work

Status: Needs work » Needs review

Status: Needs review » Needs work
berdir’s picture

Status: Needs work » Needs review
StatusFileSize
new34.02 KB

Reroll, minor conflict in WebTestBase.

dawehner’s picture

Status: Needs review » Reviewed & tested by the community

The question from crell got answered

alexpott’s picture

Category: Task » Bug report
Priority: Normal » Major
Status: Reviewed & tested by the community » Fixed

Committed 9a6582b and pushed to 8.0.x. Thanks!

diff --git a/core/lib/Drupal/Core/PageCache/DefaultRequestPolicy.php b/core/lib/Drupal/Core/PageCache/DefaultRequestPolicy.php
index fd79505..aec8d45 100644
--- a/core/lib/Drupal/Core/PageCache/DefaultRequestPolicy.php
+++ b/core/lib/Drupal/Core/PageCache/DefaultRequestPolicy.php
@@ -21,7 +21,7 @@ class DefaultRequestPolicy extends ChainRequestPolicy {
   /**
    * Constructs the default page cache request policy.
    *
-   * @param \Drupal\Core\Session\SessionConfiguration $session_configuration
+   * @param \Drupal\Core\Session\SessionConfigurationInterface $session_configuration
    *   The session configuration.
    */
   public function __construct(SessionConfigurationInterface $session_configuration) {
diff --git a/core/lib/Drupal/Core/Session/SessionConfiguration.php b/core/lib/Drupal/Core/Session/SessionConfiguration.php
index 7e4a8ce..9de1603 100644
--- a/core/lib/Drupal/Core/Session/SessionConfiguration.php
+++ b/core/lib/Drupal/Core/Session/SessionConfiguration.php
@@ -61,6 +61,9 @@ public function getOptions(Request $request) {
   /**
    * Returns the session cookie name.
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   *
    * @return string
    *   The name of the session cookie.
    */
diff --git a/core/lib/Drupal/Core/Session/SessionConfigurationInterface.php b/core/lib/Drupal/Core/Session/SessionConfigurationInterface.php
index 417ffd4..413b8f8 100644
--- a/core/lib/Drupal/Core/Session/SessionConfigurationInterface.php
+++ b/core/lib/Drupal/Core/Session/SessionConfigurationInterface.php
@@ -35,6 +35,9 @@ public function hasSession(Request $request);
    *
    * @see \Symfony\Component\HttpFoundation\Session\Storage\NativeSessionStorage::__construct()
    *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The request.
+   *
    * @return array
    *   An associative array of session ini settings.
    */

Fixed some docblocks on commit.

  • alexpott committed 9a6582b on 8.0.x
    Issue #2347877 by znerol, Berdir: Move DrupalKernel::...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.