Thanks to visit codestin.com
Credit goes to github.com

Skip to content

[HttpKernel] Use the existing session id if available. #45394

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 12, 2022
Merged

[HttpKernel] Use the existing session id if available. #45394

merged 1 commit into from
Apr 12, 2022

Conversation

trsteel88
Copy link
Contributor

Q A
Branch? 5.4
Bug fix? yes
New feature? no
Deprecations? no
License MIT

Session id is being overwritten by listener even if it is already set.

I have an application that is overriding the session id when the session factory creates the session. However, when this listener runs, it's overriding the session id that has already been set.

@carsonbot
Copy link

Hey!

I think @alexander-schranz has recently worked with this code. Maybe they can help review this?

Cheers!

Carsonbot

Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @alexander-schranz could you please have a look?

@carsonbot carsonbot changed the title Use the existing session id if available. [HttpKernel] Use the existing session id if available. Feb 13, 2022
Copy link
Contributor

@alexander-schranz alexander-schranz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are currently multiple reasons why we are not calling getId here or check for an existing session id in the session storage.

The first reason is that we can not be 100% sure that the sessionId was reseted here:


If it is not reseted correctly there $sess->getId() could return a previous users session id from a previous requests (off course only in swoole, roadrunner not in traditional php-fpm process, but there it could be critical). So that is why we are not checking for $sess->getId() instead we do \PHP_SESSION_ACTIVE !== session_status() to check if a session is already started and then do nothing in that case as a bridge to a legacy application session in a traditional fpm/fastcgi setup.

The second reason is that $sess->getId() will triggering a session id generation and so would generate an unneeded additional session id when a session id is given. Not sure about the whole effects about generating unused session ids but wanted to avoid here. But still calling $sess->setId(''); because of reasons above to be sure the session id is in a reseted state at the start and has no data of previous users requests in it.

@trsteel88 Can you provide more information about your use case? In which cases you run into problem and in which cases no session id is in the cookies and but a session id is but strangely in your case not started?

@nicolas-grekas In general I must say PHP itself is missing one important function about session itself. Why there is session_abort, session_unset, session_destroy, session_reset, $_SESSION = []. The only way to remove the session id is currently session_id(''); and only possible before the headers where send so something echo "test";, var_dump("test"); is already killing that listener. If php would add a function which allows to reset the session variables, id in general after a request is send it would things less flacky / hacky here and we only need todo here:

if ($cookieSessionId) {
     $session->setId($cookieSessionId);
}

And so it would only overwrite when a cookie session id is given.

@trsteel88
Copy link
Contributor Author

@alexander-schranz I have a multi tenanted application which I'm sharing sessions across multiple domains.

A user will login to domain1.com and manage content. Within that same area, they can switch to another site (e.g. domain2.com). When the user switches, we sign a URL and send the original session id to domain2.com.

When the user lands on domain2.com, we are calling $session->setId('original-session-id').

The above works fine at the moment.

The issue arises when the user already has an active session on domain2.com.

e.g. they have logged into domain2.com. The open a new tab and visit domain1.com and login. Now, if they switch from domain1.com to domain2.com, the user lands on the signed URL but we can't use the session ID because the cookie is taking preference.

@alexander-schranz
Copy link
Contributor

@trsteel88 The $session->setId('original-session-id'); should still work in your case as it is called after your the cookie session id was set and so would overwrite the cookie session id. But maybe we have problems with subrequest here are you losing your set session id inside a subrequest? Can you give the following a try:

            $request->setSessionFactory(function () use (&$sess, $request) {
                if (!$sess) {
                    $sess = $this->getSession();

                    /*
                     * For supporting sessions in php runtime with runners like roadrunner or swoole, the session
                     * cookie needs to be read from the cookie bag and set on the session storage.
                     *
                     * Do not set it when a native php session is active.
                     */
                    if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== session_status()) {
                        $sessionId = $sess->getId() ? $sess->getId() : $request->cookies->get($sess->getName(), '');
                        $sess->setId($sessionId);
                    }
                }

                return $sess;
            });

@trsteel88
Copy link
Contributor Author

The session is not active when I'm calling it. I'm overriding the NativeSessionFactory and have an event that is fired when the createStorage is called.

This allows me to then listen to that event elsewhere.

In my case, the session has not started yet and that's why the interfering code is overriding my value.

@trsteel88
Copy link
Contributor Author

@trsteel88 The $session->setId('original-session-id'); should still work in your case as it is called after your the cookie session id was set and so would overwrite the cookie session id. But maybe we have problems with subrequest here are you losing your set session id inside a subrequest? Can you give the following a try:

            $request->setSessionFactory(function () use (&$sess, $request) {
                if (!$sess) {
                    $sess = $this->getSession();

                    /*
                     * For supporting sessions in php runtime with runners like roadrunner or swoole, the session
                     * cookie needs to be read from the cookie bag and set on the session storage.
                     *
                     * Do not set it when a native php session is active.
                     */
                    if ($sess && !$sess->isStarted() && \PHP_SESSION_ACTIVE !== session_status()) {
                        $sessionId = $sess->getId() ? $sess->getId() : $request->cookies->get($sess->getName(), '');
                        $sess->setId($sessionId);
                    }
                }

                return $sess;
            });

This works. I'll update the PR.

@alexander-schranz
Copy link
Contributor

Okay some tests are now failing. I need also check what effect this change will have on swoole and roadrunner applications. The Surrogate test sounds we maybe run into problems with ESI or subrequests with this change, or that maybe the test is wrong. Not sure if I will have time before end of the week to have a look at it.

@fabpot
Copy link
Member

fabpot commented Mar 26, 2022

@alexander-schranz Do you think you will have time to have a look at this one soon?

@alexander-schranz
Copy link
Contributor

@fabpot I will try to have a look at it tomorrow.

@alexander-schranz
Copy link
Contributor

Update here tested roadrunner and swoole and the change seems not have any effects on them.

The following patch should fix the tests, I have no permissons to push it:

commit 625f2b7e31fa242b515aa0cea13574a68f1d07a5
Author: Alexander Schranz <[email protected]>
Date:   Thu Apr 7 01:47:40 2022 +0200

    Fix surrogate test cases for session listener

diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php
index 3bf31bfe68..f0609316b4 100644
--- a/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/SessionListenerTest.php
@@ -522,7 +522,7 @@ class SessionListenerTest extends TestCase
     public function testSurrogateMainRequestIsPublic()
     {
         $session = $this->createMock(Session::class);
-        $session->expects($this->exactly(2))->method('getName')->willReturn('PHPSESSID');
+        $session->expects($this->exactly(1))->method('getName')->willReturn('PHPSESSID');
         $session->expects($this->exactly(4))->method('getUsageIndex')->will($this->onConsecutiveCalls(0, 1, 1, 1));
 
         $container = new Container();
@@ -562,7 +562,7 @@ class SessionListenerTest extends TestCase
     public function testGetSessionIsCalledOnce()
     {
         $session = $this->createMock(Session::class);
-        $session->expects($this->exactly(2))->method('getName')->willReturn('PHPSESSID');
+        $session->expects($this->exactly(1))->method('getName')->willReturn('PHPSESSID');
         $sessionStorage = $this->createMock(NativeSessionStorage::class);
         $kernel = $this->createMock(KernelInterface::class);

@trsteel88
Copy link
Contributor Author

Updated @alexander-schranz

@nicolas-grekas
Copy link
Member

Thank you @trsteel88.

@nicolas-grekas nicolas-grekas merged commit 1f15392 into symfony:5.4 Apr 12, 2022
This was referenced Apr 27, 2022
@adrienfr
Copy link
Contributor

adrienfr commented Apr 28, 2022

Hi,

I'm on SF 5.4.8 and it seems this PR has changed a behavior during PHPUnit tests.
Here is my test :

public function testCountUserUnreadConversations()
{
    $user = $this->em->getRepository(User::class)->findOneByEmail('[email protected]');
    // Not logued
    $this->client->request('GET', '/user/count-unread-conversations', [], [], ['HTTP_X-Requested-With' => 'XMLHttpRequest']);
    $this->assertFalse($this->client->getResponse()->isSuccessful());
    // No Ajax
    $this->setLoggedClient($user);
    $this->client->request('GET', '/user/count-unread-conversations');
    $this->assertFalse($this->client->getResponse()->isSuccessful());
    // Ok
    $this->client->request('GET', '/user/count-unread-conversations', [], [], ['HTTP_X-Requested-With' => 'XMLHttpRequest']);
    $this->assertTrue($this->client->getResponse()->isSuccessful());
    ...
}

If was successful in previous versions but since 5.4.8, the last assertion fails, the $user is no more logged and the response is no more a 200 but a 302 redirect to /login.

The setLoggedClient function is only calling $client->loginUser() and I'm on PHP 7.4.25:

protected function setLoggedClient(User $user)
{
    $this->client->loginUser($user, 'primary_auth');
}

I'll create a bug report and a reproducer, unless you may have an idea of what's happening?

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants