Problem/Motivation
In #2205295: Objectify session handler and #2228341: Objectify session management functions + remove session.inc the procedural code from session.inc
has been converted to SessionHandler
and SessionManager
. However the new classes still have too many responsibilities. For example both of them access and set the global $user
object. Setting the current user is clearly the responsibility of the authorization manager service.
In order to decouple session management from user authentication, we need a way to pass user-information read from the session table by the SessionHandler
through to the user authentication system.
The way this is done in Symfony differs a little bit from the Drupal implementation. For example, the Symfony documentation states that...
Once the user is logged in, the entire User object is serialized into the session.
as well as:
In theory, only the id needs to be serialized, because the refreshUser() method refreshes the user on each request by using the id (as explained above). However in practice, this means that the User object is reloaded from the database on each request using the id from the serialized object.
In the Drupal implementation the session metadata (user id, hostname, timestamp) is stored separately from session data. Therefore we need a way to pass this information along with the session.
Proposed resolution
Register necessary Symfony session services and associate the session object to the request from within a http middleware that also cleans up the session afterwards.
In a second step (likely followup) refactor the SessionHandler
and SessionManager
to act on the session metadata bag instead of manipulating the global $user
directly. Also move the responsibility of actually changing the user to the authentication manager.
Remaining tasks
User interface changes
API changes
Follow-up from #2228341: Objectify session management functions + remove session.inc.
Beta phase evaluation
Issue category | Task because its part of the continuoes work on sessions. |
---|---|
Disruption | Nearly none, beside people using the various objects directly, but people saving session data don't have to change their code. |
Comment | File | Size | Author |
---|---|---|---|
#119 | 2229145-symfony-session-facade-118.patch | 20.72 KB | neclimdul |
#119 | 2229145-interdiff-118.txt | 2.09 KB | neclimdul |
#110 | 2229145-interdiff-110.txt | 2.62 KB | neclimdul |
#110 | 2229145-symfony-session-facade.patch | 21.1 KB | neclimdul |
#104 | interdiff.txt | 3.76 KB | znerol |
Comments
Comment #1
znerol CreditAttribution: znerol commentedComment #2
znerol CreditAttribution: znerol commentedI realize that the Symfony Session requires a service implementing
SessionStorageInterface
, therefore we first need to adapt the DrupalSessionManager
to theSessionStorageInterface
.Comment #3
znerol CreditAttribution: znerol commentedNeeds to wait for #2238087: Rebase SessionManager onto Symfony NativeSessionStorage
Comment #4
jibranBack to active again.
Comment #5
neclimdulHere you go. Session tests and phpunit tests pass. Lets see what the test bot can do with everything else.
I wouldn't say this is a perfect patch. I wasn't able to completely get rid of the manager interface which I see as sort of a sign we haven't completely made it through the conversion but this meets the goals of this issue at least.
Comment #6
neclimdulWoops, missed a file.
Comment #7
neclimdulhttps://github.com/neclimdul/drupal/commits/symfony_session for commits to get here. Tried to make the steps I took as clean and explicit as possible.
Comment #10
znerol CreditAttribution: znerol commentedI think we need to get in #2272987: Do not persist session manager before.
Comment #11
joelpittet#2272987: Do not persist session manager Is in, this needs a re-roll.
Comment #12
znerol CreditAttribution: znerol commented#2338571: Remove SessionManager::startLazy() will help here. Also please note that we already discussed naming in #2238087: Rebase SessionManager onto Symfony NativeSessionStorage.
Comment #13
znerol CreditAttribution: znerol commentedTried to reroll #6 but instead started over because of too many conflicts. Also I believe we really should keep issues like this scoped, i.e. no renames and no refactorings if they are not strictly required to fulfill the goal.
I agree that we should find a dedicated home for the mixed-mode-ssl stuff, I also agree that the session write protection needs to be decoupled (see #2338727: Replace static SessionManager::$enabled property with WriteSafeSessionHandler class and resolve hidden circular dependency between SessionManager and SessionHandler) and indeed, the session handler stack needs to be exposed in the container at some point. But let's focus on the introduction of the Symfony
Session
facade in order to have the API in place.Instead of using a subscriber, I propose to introduce a stack middleware. This will turn green as soon as #2230121: Remove exit() from FormBuilder is resolved.
Comment #14
znerol CreditAttribution: znerol commentedComment #16
znerol CreditAttribution: znerol commentedReroll.
Comment #18
znerol CreditAttribution: znerol commentedThis looks like trouble caused by persisting services (request referencing the old session across container rebuild). Let's see whether this is true.
Comment #20
znerol CreditAttribution: znerol commentedWrong patch.
Comment #24
znerol CreditAttribution: znerol commentedOnly access the session service if the previous request had a session.
Comment #26
znerol CreditAttribution: znerol commentedNow that the cookie provider pulls the session from the request, it is necessary to also ensure that the session is set on the request from within
prepareLegacyRequest()
.Comment #28
znerol CreditAttribution: znerol commentedPrevent that the
session
service is accessed in the early installer.Comment #29
znerol CreditAttribution: znerol commentedChange
Authentication\Provider\Cookie::apply()
to returnTRUE
only if there is a session on the request.Comment #30
dawehnerLet's add more semicolons, what about 12? :)
It would be great to explain why we have to still set the session here. Isn't that covered by the session middleware + prepareLegacyRequest?
There is certainly a verb missing in here :) Can you explain when you would actually not need this here? Would drush be an example of it?
... so we cannot typehint the interface because of
SessionInterface::getFlashBag
pretty neat!Can you add some short comment about why we check for just the master request?
So this should have been done before?
Comment #31
martin107 CreditAttribution: martin107 commentedComment #32
znerol CreditAttribution: znerol commented$use_session
is the early installer lacking a database. On closer inspection it seems to me that it is not really appropriate to callprepareLegacyRequest()
there at all because it tries to boot the kernel a second time, pushes the request on the stack again and whatnot. Instead we could just sidestepprepareLegacyRequest()
and directly callpreHandle()
.SessionListener
. Session is as global as something can be in PHP, so there is not much of a point dragging that through the request stack?session_destroy()
with its Symfony equivalent which isSession::invalidate()
. Seems like there is something missing inSessionManager::regenerate
when$destroy
parameter isTRUE
.Comment #33
znerol CreditAttribution: znerol commentedUhm, #12375
Comment #34
znerol CreditAttribution: znerol commentedIn Symfony the session is intended to be destroyed with
Session::invalidate()
. Regrettably this method is currently broken (see Symfony issue #12375) and may lead to the creation of spurious session records in the database. Therefore revert to usesession_destroy()
instead and also revert the changes toSessionManager::regenerate()
.Comment #35
znerol CreditAttribution: znerol commentedAfter the request has been handled, save the session which is present on the request.
Comment #36
Crell CreditAttribution: Crell commentedI'm not entirely clear on this comment. I read it as "we should do this in a middleware but we'll do it here instead", which seems odd because we do have a middleware.
Other than that, I'm happy here. znerol++
Comment #37
znerol CreditAttribution: znerol commentedRe #30.4, let's just type-hint
SessionInterface
. After all this is what$request->setSession()
and it is also the declared return type of$request->getSession()
. As pointed out in pr 12420, Symfony itself wrongly expects thegetFlashBag()
method in several places without checking forSession
instead of SessionInterface.Re #30.5 I talked to @Tobion (Symfony) about this and he pointed out that in Symfony, the code initiating a subrequest is responsible for carrying over the session. This is done automatically when using
$request->duplicate()
because that uses cloning (One example is the Controller::forward() in SymfonyFrameworkBundle
. The occurrences ofRequest::create()
in non-test Symfony code are not quite consistent. For example InlineFragmentRenderer::createSubRequest() carries the session over to the subrequest while Esi::handle() does not.In Drupal there are two different use-cases where
Request::create
is called. First, there are occurrences where the created request object is passed toHttpKernel::handle()
. Those include DefaultExceptionHtmlSubscriber::makeSubrequest() and CommentController::commentPermalink(). In the long run those should maybe use$request->duplicate
and pass in the controller directly, just likeController::forward
of theFrameworkBundle
. This is only possible though when system paths can be eliminated entirely. In the meantime the session should be carried over to the subrequest in those two instances.The second use case is resolving paths to routes. This is done in PathValidator::getUrl, Shortcut::preSave() and PathBasedBreadcrumbBuilder::getRequestForPath(). The latter two in fact should be using the
PathValidator
instead. In addition AccessAwareRouter::match() instantiates a new request. Unfortunately there is no reference to the parent request and thus carrying over the session is impossible in that case. Granted that PathValidator::getPathAttributes() calls$router->match()
, there is no way to propagate the session for any instance mentioned in this paragraph. As of #2331079: Use RouteMatch in access-checks and remove RequestHelper::duplicate() access checks which require access to the request are only when actually checking an incoming request. That means that the$request
is not available to access checkers when validating paths in the first place, therefore IMHO it is legit to not propagate the session in that case.Re #36 the function context line of this hunk is a bit confusing. That specific comment is in
prepareLegacyRequest()
not inhandle()
.Comment #39
znerol CreditAttribution: znerol commentedFix copy paste accident.
Comment #40
Crell CreditAttribution: Crell commentedLet's do then.
Comment #41
alexpottThis issue is a normal task so we need to outline how it fits within the allowable Drupal 8 beta criteria. Can someone add Drupal 8 beta phase evaluation template to the issue summary.
And we need a change record here that details the 8.x to 8.x changes as well as going through the existing CRs and making sure the 7.x to 8.x is covered and correct.
Comment #42
dawehnerRaised the priority.
Comment #43
alexpottAfaics we will need to update https://www.drupal.org/node/2228871? Can someone else confirm and attach any other CRs that need updating.
Comment #44
znerol CreditAttribution: znerol commentedRe #43 yes, 2228871 needs to be updated. Additionally filed https://www.drupal.org/node/2380327.
Removing the "Needs issue summary update" tag, this was addressed in #42 I guess.
Comment #45
znerol CreditAttribution: znerol commentedReroll.
Comment #47
jibranSorry but #45 was not a proper reroll
Comment #48
znerol CreditAttribution: znerol commentedOh, right. Let's try again.
Comment #49
znerol CreditAttribution: znerol commentedComment #51
znerol CreditAttribution: znerol commentedReplicate the changes introduced into
prepareLegacyRequest
by #2362227: Replace all instances of current_path() to the installer.Comment #52
dawehnerCan you make at least a
@see prepareLegacyRequest
from which this code is copied from?So everyone who does a subrequest would need to do that? Given that this sounds like a generic functionality we need somewhere.
Comment #53
larowlanI agree with @dawehner - can we automate this with an event subscriber?
This is a fairly major change in logic - can you elaborate why it is needed?
Comment #54
larowlanFixes #52 - attempts to add event subscriber to hand down session from parent requests to sub-requests.
Comment #55
larowlanunneeded
help if you added this to core.services.yml
Comment #56
larowlanso how in the world did #54 pass?
Comment #57
larowlanthe subscriber seems to work - manual testing of comment permalink
Comment #58
znerol CreditAttribution: znerol commentedRe #52.2 and the subsequent changes by @larowlan: This is answered in #37. I do not see any reason why we should deviate from Symfony in this regard. Should Drupal decide to ignore that, then there is a much better solution than introducing
SubRequestSessionSubscriber
: Kernel middlewares are also executed on subrequests, and hence theSession
middleware just could take care of populating the request session regardless of request type.Re #53.2 this is a bugfix on untested code. Currently there is no non-metadata session bag and therefore this bug went undetected before.
Comment #59
larowlanSo we need 51 with the docs changes of 54?
Comment #60
neclimdulI was going to do the reroll but I ran into a problem.
@see doesn't clearly convey any meaning in this context without reading this issue. Especially since its missing so much of the logic from that method so any documentation you could gleam from that method looses meaning. I think it would be better to write clearer documentation of what we're doing and then if we really feel strongly document that it is similar to or loosely based on the other method.
Comment #61
neclimdulI played around to better understand how to document this and the routing properties don't seem necessary. I guess the routing properties are sort of a "just in case" sort of thing.
Needed a reroll after #2347877: Move DrupalKernel::initializeCookieGlobals() into a SessionConfiguration service so that's done too.
Comment #62
cpj CreditAttribution: cpj commentedI reviewed the code and have tested the patch from #61 in a similar fashion to #57. I have also stepped through the code in the debugger of my IDE to understand what the StackMiddleWare/Session is doing. As far as I can tell is works as expected, but what about the additional code in #55 ? Is this still needed (I think so...)
Comment #63
cpj CreditAttribution: cpj commentedI've just noticed that the class explanation text, lines 14 - 16 of StackMiddleware/Session.php will need to change. The current text is the same as StackMiddleware/PageCache.php
Comment #64
cpj CreditAttribution: cpj commentedIn #55, line 10
Typo - I assume this should be session.subrequest_session_subscriber
Does anything use this service yet ?
Comment #65
neclimdulI made the assumption that we didn't need #55
Comment #66
znerol CreditAttribution: znerol commentedI agree.
Comment #67
cpj CreditAttribution: cpj commented@neclimdul & @znerol - thanks for the clarification. So I review & tested #65 with the latest HEAD and it works in our environment, so this looks good-to-go to me
Comment #68
joelpittet#65 re-roll
patch -p1
worked, butgit apply
didn't.Is there any manual testing we could do to confirm this is good to go? It looks great:)
Comment #69
cpj CreditAttribution: cpj commentedIs there some reason why this can't be marked as "Reviewed & tested by the community" ?
Comment #70
joelpittet@cpj nope, if you have reviewed and tested, then you can set the status;)
Comment #71
almaudoh CreditAttribution: almaudoh commentedLet me take a look at it...
Comment #72
almaudoh CreditAttribution: almaudoh commentedComment #73
cpj CreditAttribution: cpj commentedComment #75
neclimdulAnother re-roll. no changes.
Comment #76
joelpittetRe-RTBC as per #73 and I double checked #68 vs #75 to see if there were any sneaky re-roll accident and there were not so if testbot agrees it will stay.
Comment #79
joelpittetTestbot hickup, thanks @larowlan
Comment #80
alexpottComment #81
alexpottDoes this code in
\Drupal\user\Entity\User
need to change too?Comment #82
almaudoh CreditAttribution: almaudoh commentedStraight re-roll
Comment #83
almaudoh CreditAttribution: almaudoh commentedWell...the actual patch
Comment #84
joelpittetBack to RTBC.
Comment #85
joelpittetThank you for rerolling @almaudoh
Comment #86
almaudoh CreditAttribution: almaudoh commentedThe change #81was already in the patch. So I'll mark this RTBC per #79.
Comment #87
joelpittetOh I missed that, @almaudoh, do you know if the other $session_manager variables are supposed to change as well for ->delete() calls? @alexpott may not have been refering that so just checking.
In
\Drupal\user\Entity\User
Comment #88
almaudoh CreditAttribution: almaudoh commentedGood point @joelpittet
Comment #89
almaudoh CreditAttribution: almaudoh commentedRight now, there is still a lot of coupling between SessionManager, SessionHandler and Sessions which would be decoupled in a followup (mentioned in the IS), these others would be in that follow up.
Comment #90
znerol CreditAttribution: znerol commentedWe cannot move
$session_manager->delete($this->id());
into theSessionHandler
because this is a method which cannot be implemented easily by some session storage implementations (e.g. memcache).I'd rather move the
delete
method into aUserSessionStorageHelper
class+service which third party storage modules may choose to provide or not.Comment #91
alexpottCommitted b926f5d and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
I've published the change record - I think we need to update https://www.drupal.org/node/2228871
Comment #93
alexpottDrats... this breaks run-tests without a database. Which is about to be used to remove the drush dependency from testbots. I have to revert because we shouldn't be creating critical followup of major issues.
Comment #95
alexpottHere's the fix. The small patch is the interdiff.
Comment #96
alexpottThe issue to remove drush from the testbots is #2206501: Remove dependency on Drush from test reviews
Comment #97
alexpottThis is problematic because session_manager depends on the database so we'll content to the database even we we don't need to.
Comment #98
znerol CreditAttribution: znerol commentedCould you explain what you mean. I do understand that.
Comment #99
almaudoh CreditAttribution: almaudoh commented#97:
I think the concern is that we would tend to make writes to the session table in the db even when there is no session or nothing in the session.
Looking thru the code, there's nothing that tells me that would happen. But there's nothing that assures me it won't happen either. However, I know one of the follow ups to be created for decoupling authentication from session management would handle this.
@znerol: You may probably have a better answer than mine.
Comment #100
alexpottSo without the additions in #95 running
$ php ./core/scripts/run-tests.sh --list
in a checkout of drupal which has not been installed - ie. no settings.php with db settings - generates the following error:This is happening because the session manager has the database injected and calling Database::openConnection(). So we're creating a connection to the db super early.
Comment #101
znerol CreditAttribution: znerol commentedInstead of injecting the session directly into the session middleware, we could inject the container. This would allow us to retrieve the session service from the container and inject it into the request conditionally, i.e. only if not running in CLI mode.
Making the session service lazy might help also.
Comment #102
alexpottre #101 but then we still have more page_cache_without_database problems, no?
Comment #103
znerol CreditAttribution: znerol commentedNo, the session middleware runs after (i.e. inside) the page cache middleware. Therefore if the session service is only initialized when its
handle()
method is invoked (instead of upon service construction), then the page cache is already done.Comment #104
znerol CreditAttribution: znerol commentedIt seems to me that guarding against
PHP_SAPI === 'cli'
is the most effective way of fixing therun-tests.sh
issue. Interdiff is against #83.Comment #105
cpj CreditAttribution: cpj commented#104 works in our environment and the latest changes make sense to me, so I'm going to mark this as RTBC. OK ?
Comment #106
cpj CreditAttribution: cpj commentedComment #107
alexpottDiscussed with @catch on IRC - he said "so we initialize the database connection just to check a cookie, yes I don't like that either." I feel the same. We should only connect to the db if the cookie exists.
Comment #108
znerol CreditAttribution: znerol commentedI do not think that this is any different than the current situation. We initialize the session manager on every request and we need to do that also in the future as long as we allow anyone to trigger a new session by simply putting stuff into
$_SESSION
.Comment #109
cpj CreditAttribution: cpj commented@alexpott #107 - interesting comment, but I think @znerol is correct. What is there in this patch that changes this situation ?
Comment #110
neclimdulYeah I think cpj and znerol are right. The database behavior hasn't changed in this patch. The problem in #100 might have been miss-stated a bit though. The problem is actually run-test.sh calling
DrupalKernel::prepareLegacyRequest()
and prepareLegacyRequest changing slightly. Enough work has gone in that we don't have to pretend that we are working with a request in run-test so really run-test doesn't need to call prepareLegacyRequest anymore we can just callboot()
andloadLegacyIncludes()
directly.Also, we can problem get rid of those PHP_SAPI checks. If something on the CLI is going through the http_kernel we've got problems.
Full disclosure for discussion, this does mean that cli tools have to assume a database connection or not use prepareLegacyRequest so that's sort of a regression. It doesn't affect drush though from what I can tell and run-test.sh was the only CLI tool we where using it in.
Comment #112
neclimdulMy bad, I was poking at some other code and forgot to remove it before making the patch.
Also, I was mistaken and some tests actually require the prepareLegacyRequests. They fail in the runner which means they're probably unit tests. Unit tests relying on global state is pretty much a bug but we can follow up to fix that. For now the runner can boot legacy request stuff when it is executing a test.
Comment #114
joelpittet@neclimdul just a small nitpick:
Double ;;.
Comment #115
znerol CreditAttribution: znerol commentedThe
PHP_SAPI === 'cli'
checks are not only fixing the problem, but they actually make sense in those places. That way we clearly communicate the fact that there is no session when running from the command line. What else do we need?Comment #116
tadityar CreditAttribution: tadityar commentedRemoved the extra ';'.
Comment #118
neclimdulClearly I was wrong that we have done enough work in run-tests to get rid of those assumptions and my spot checks where not representative. The attached patch rolls back those CLI checks and run-tests changes and is the last RTBC'd patch#104 with a spelling fix.
To re-address alex and catch's concerns from when it was bumped NR, the database behavior is unchanged and if that is a problem it was an earlier regression not attached to this improvement. Since this basically gets us to the architecture we've been working for I would prefer if we can address that problem with in a separate issue with profiling and specific review. We might be able to just use a lazy proxy or something but we should better understand the problem.
Comment #119
neclimdulthat moment when you realize you where distracted by an email and forgot to attach the patches.
Comment #120
neclimduland the status. please confirm things are working testbot.
Comment #121
cpj CreditAttribution: cpj commented@neclimdul #118 - I agree that this is not the right place to start optimizing / fixing database behavior concerns. This patch changes nothing in that regard so "no harm, no foul", right ? But I'd be totally up for helping on a separate issue in that direction, if someone was to open one...
Comment #123
catchAre we sure that the middleware won't be instantiated on page cache hits? That would be a regression compared to the status quo too (if using memcache database backends + database session storage for example).
I opened #2424451: Session manager (and others) should not initialize database connection if no query needs to be run for the more general problem.
Comment #124
cpj CreditAttribution: cpj commented@catch #123 - I haven't been testing with caching enabled, so to be honest I'm not sure if the behavior is different than before on page cache hits... Logically I'm not sure why this patch should change what happens on page cache hits but I will try to take a look at it...
Comment #125
catchIt depends whether all the middlewares get instantiated before any particular middleware gets executed or not.
Comment #126
znerol CreditAttribution: znerol commentedNot so much of a regression, with #104 the database will not be initialized for page cache hits. The session middleware class will be loaded though, but nothing more.
Thats true, all middlewares are instantiated the moment when the stacked kernel is instantiated. That is also the reason why we do not inject config into the page cache middleware, and why I moved out the session dependency in #104. Note that contrib can destroy your whole page cache performance when introducing middlewares with heavy dependencies.
That said, if we want to have the session on the request, then we need to put it onto the request somewhere. This needs to happen before authentication, i.e. before anything tries to log something (at the moment) or before routing (when #2286971: Remove dependency of current_user on request and authentication manager lands).
Comment #128
neclimdulThere's a random failure in HEAD it seems. #2424743: Random testbot fail: "The page does not have double escaped HTML tags." in Drupal\field_ui\Tests\ManageFieldsTest.
Thanks catch, that's exactly the issue I was thinking and a great summary.
In case it wasn't clear because it wasn't in the comment, in #104 the patch has the middleware take the container instead of the session objects so it only creates services when its run and after it knows it needs them.
Comment #130
almaudoh CreditAttribution: almaudoh commentedCore committer concerns raised in #107 have been addressed with a follow-up. The currrent patch does not introduce any regressions from HEAD and further optimizations can still be done in followups. Patch has been tested by @cpj in a dev environment and is ok.
Since all I did in this patch was a re-roll, I believe I can go ahead and RTBC this.
Comment #131
cpj CreditAttribution: cpj commentedSo I've not tested it again, but I've reviewed the latest version and there appear to be no substantial changes to the version I tested, so I agree this should be marked as RTBC as suggested by @almaudoh in #130.
Comment #132
alexpottGiven that my contribution to this patch was only fixing run-tests.sh I think it is fine for me to commit.
Committed 7f6f61f and pushed to 8.0.x. Thanks!
Thanks for adding the beta evaluation to the issue summary.
In discussion with @catch we decided to change the doblock on commit.
Comment #134
almaudoh CreditAttribution: almaudoh commentedWooot! :) Thanks @alexpott. We're getting there.
Comment #135
cosmicdreams CreditAttribution: cosmicdreams commentedAfter this change, what uses SessionManager? I have attempted to find what code instantiates the SessionManager and have only found service container (generated) code that uses it.
Does that mean that we should remove the SessionManager in a follow up issue?
Edit: Found one reference to SessionManager's enable method in \Drupal\Core\Session\AccountSwitcher but that's the only one so far.
Comment #136
cosmicdreams CreditAttribution: cosmicdreams commentedAh looks like there's a follow up that addresses some of this: #2426031: Remove deprecated uses of SessionManager::isEnabled(), SessionManager::enable() and SessionManager::disable()
Comment #138
Wim LeersRelated: #2597520: Don't save the session before $response->send().
Comment #139
jibranWe have a @todo against this issue in
core/lib/Drupal/Core/Session/SessionManager.php
Do we have an issue to remove this?
Comment #140
longwaveCouldn't find an issue so opened #3206358: Remove bag initialization in SessionManager