Closed (cannot reproduce)
Project:
Remember me
Version:
7.x-1.x-dev
Component:
Code
Priority:
Normal
Category:
Feature request
Assigned:
Unassigned
Reporter:
Created:
5 Sep 2013 at 09:02 UTC
Updated:
25 Feb 2014 at 17:11 UTC
Jump to comment: Most recent, Most recent file
Comments
Comment #1
znerol commentedComment #2
znerol commentedOkay, here is an improved attempt. In this version we even get rid of the
_remember_me_set_lifetimefunction which relied on session-reinitialization in order to influence session cookie lifetime.The new approach is based on the observation, that we only have to make sure that
ini_set('session.cookie_lifetime', X);is called beforedrupal_session_regenerate()when logging in. We ensure this by injecting a#submit-hook into the login form which is called beforeuser_login_submit().Because I was touching
hook_form_alteranyway I cleaned that up a little bit.Comment #3
nickl commentedNice can of worms you've opened up here. In all the time we've had remember_me you are the first to bring this issue up.
I will have to ponder this...
It is difficult to asses what replaces what exactly as the patch seems to be a complete rewrite.
This is where separate commits have us spoilt on GitHub.
I am all for less code make no mistake but have you still covered all your bases?
Some questions from an initial patch sweep:
Why did you choose 2 hook_form_FORM_ID_alter functions + helpor function over the current implementation of 1 hook_form_alter?
The changing of the submit handler seems a bit agressive is this really necessary?
How does this effect other types of logins (i.e. open_id/oAuth/etc)?
Why did you choose this approach over the hook_user_login?
I notice the tabindex has been removed is this functionality regained by the weights? In all forms?
Your patch now ignores the ini setting completely as configured in settings.php and assumes default is 0, why is that?
You are setting the cookie lifetime on every request now regardless of whether it already has the correct lifetime or not, are there no implications to this?
Is it also required to fix session cookie lifetime on submit even though init is handling this?
Does the submit handler happen before or after init?
Now what to do with all these worms? =)
Comment #4
znerol commentedI assume that you are capable of reproducing the steps given in the issue description. Just use two different browsers (one for home and one for internet cafe), or open a private browsing session. Therefore I won't discuss the
$_SESSIONvs.$user->datacase, it should be pretty obvious.I also will leave out the cosmetic changes (in
hook_form_alter). You are definitely right, atomic changes are safer and easier to understand.Lets focus on the core functionality of this module. I'll start out with an analyze how the login-process in Drupal works.
User login with remember me disabled
$uservariable and setting the session-cookie). The cookie lifetime is set here to whateverini_get('session_cookie.lifetime');returns.hook_user_loginis invokeduser/[uid]. For the further analysis it is important to keep in mind, that the initialPOSTrequest is closely followed by thisGETrequest issued through a redirect.Looking at the requests using Firefox and Life HTTP Headers (or Wireshark, or whatever network analyzer at hand), you'll notice that the session cookie is set in the response to the POST-request. On subsequent requests, the session cookie is not set anymore by the server. This is expected behavior.
User login with remember me enabled
With the Remember Me module enabled you have the exact same sequence of events until step 5. That means when
hook_user_loginis called it is actually too late to influence the cookie session lifetime - at least for the current request. TheSet-Cookieheader of the response to thePOSTrequest will have the cookie lifetime which is configured insettings.php(by default 2000000 which is a bit more than 23 days). However because the initialPOSTis followed immediately by a redirectedGETrequest where the cookie-lifetime is fixed from withinhook_initthe initial (wrong) cookie lifetime does not stand out - at least one would not notice it by just looking at the page resource using Firebug / Page inspector tools.Note that fixing the cookie-lifetime as implemented by the remember me module (see
_remember_me_set_lifetime) means restarting the session and effectively sending aSet-Cookieheader to the client. Don't take me wrong, but the implementation of this function made me quite a bit nervous.When looking at the requests going forth and back between browser and server using a network analyzer, one will notice that the
Set-Cookieheader is not only set twice during login, but also on every subsequent request. I think thathook_initintents to avoid calling into_remember_me_set_lifetimewhen its not necessary, but it obviously fails.The reason for the frequent calls to
_remember_me_set_lifetimeis thatini_get('session.cookie_lifetime')as called fromhook_initalways returns the value configured from withinsettings.php.Takeaways
hook_user_loginis called.Technically it might even be unnecessary to alter the
session.cookie_lifetimeparameter from withinhook_init. It cannot influence session-start anyway. However there might be third party code setting cookies with the same parameters as session cookies, therefore it is probably better to keepsession.cookie_lifetimein sync with the user-session preference.Answers to your questions
Cosmetics, has no relevance.
Yes, see above.
I don't know.
Because
hook_user_loginis called too late, see above.Cosmetics, has no relevance.
Ah, I obviously misunderstood the intention of the
remember_me_managedvariable. I will address that when rerolling the patch(es).Changing the setting only has an effect when a function relying on it is called afterwards. Sessions are started before, except when sessions are regenerated afterwards (like during user login).
If we want to influence the
Set-Cookieheader in the firstPOSTresponse, we need to inject our own submit handler before the built-in one. Also changing the value duringinitdoes not change the cookie parameters of a cookie set during a previous response.The sequence is at follows:
hook_init(no user logged in),submit-handler(change user, set cookie),hook_user_login(too late to influence cookie lifetime).I will attach atomic patches in a follow up.
Comment #5
znerol commentedFirst patch: Use
$_SESSIONinstead of$user->datafor remember_me flagSecond patch: Fix cookie lifetime during login and not on subsequent page requests
Comment #6
nickl commentedSome thoughts so far:
No need to change the session on login submit, this is followed by a redirect which can set the lifetime. The same functionality that allows you to be remembered in future.
Session cookie lifetime is the lifetime the browser saves the cookie for this is not the same as expired sessions on the server.
Since settings.php will always set the session. cookie_lifetime it makes sense that it will always first be incorrect and needs to be changed. This becomes especially important for not being remembered or cookie_lifetime = 0. At hook_init the session was already created hence the recreate (which is std php b.t.w and a good protection against man in the middle anyway so no need for concern) and unless we can hook in before that I don't see how we can avoid it. Drupal hooks are chained where as modifying the session handler functions is arguably not going to play nice with others. Is it enough to create the session once only? I still need to verify this.
Comment #7
znerol commentedDo you agree on having the flag in
$user->data['remember_me']is wrong? Then we could building up a solution from there.Comment #8
nickl commentedWhy does this make you nervous? In fact if nothing else changing the session id on every request is good protection against man in the middle attacks. There is nothing that says the session id should remain the same only that you must provide me with the correct id that matches the current state we are keeping this request on your next request. After that we can safely change the session id again and again and again.
I cannot reproduce the cross location (different browser) remembered session as the two different browsers have different session ids and they are not able to log into each others sessions.
Comment #9
dwillems commentedI see that this has been closed but I'd like to make an appeal. Is there any chance of getting the $_SESSION patch committed alone? I have implemented it on a site currently in development and no issues have come up in testing. We've seen a big performance boost since it removes a call to user_save which otherwise adds a lot of unnecessary overhead to the login operation, especially when fields are added to users.
If moving the flag from $user->data is undesirable, an alternate workaround could be to update the data field by making a separate query, similar to how user_login_finalize updates the login timestamp without calling user_save.
Thanks!
Comment #10
alanmackenzie commentedI would like to echo dwillems comments.
I have a Drupal site with 1.2 M users and a fair number of fields related to users so user_save() was causing us issues. To me using $_SESSION sounds like a sensible idea.
Comment #11
nickl commentedThe drupal hook system is specifically there for module developers to use. This allows for compatibility between different modules and other customisations. If you wanted the data in $_SESSION for example you could catch this with presave.
Without tangible benchmark figures it is not possible to assess why this is a performance issue, please present your findings. This will only effect the user authentication process and has no bearing on general performance. Do you have a problem with the time it takes to authenticate?
If there is an issue with using user_save then I doubt this is where we should address the problem and perhaps it should be taken up with fields or core. Just a thought....
Bypassing the hooks is no way to play nice with others and we wouldn't want that, do we?
Comment #12
nickl commented@dwillems I don't see any patch that only changes the storage of the remember_me token to consider.
The current implementation works with many different ways one may attempt to log into drupal which I fear removing the other functionality may have broken, unless you can confirm otherwise.
Comment #13
dwillems commented@nickl,
100% agree on your point about playing nice and not bypassing hooks (I have hundreds implemented in the project implementing remember_me).
Just to clarify, the point I am trying to make is to avoid the costly user_save, not necessarily to put the flag in $_SESSION. This could be done a number of different ways. Another solution (aside from using $_SESSION or a query to update the data field) could be to create a new table for remember_me to store the per-user flag and creating new hooks if needed for other modules to react to it.
The issue here is that the behavior of the login operation is being altered by remember_me to call user_save() when it wouldn't otherwise be called and that is a potentially very expensive call. On a small site with relatively low traffic and little or few fields on the user object this may have a minimal performance hit, but per your request I've added a screenshot comparing the profiling data on a site with many user fields. Specifically in our site, we see the patched version of remember_me_user_login taking a fraction of a millisecond to run, but just over 2.5 seconds when it calls user_save. This is not necessarily an issue with the user or fields module, it's just the nature of saving entities to the database - with many fields come many INSERT queries.
The patch I am referring to was submitted by @znerol in #5:
https://drupal.org/files/0001-Use-_SESSION-instead-of-user-data-for-reme...
Comment #14
alanmackenzie commentedYes.
Thanks to dwillems for his numbers. I do not think anyone is suggesting that remember_me is causing site wide load issues. A slow authentication process gives the perception of a slow site, unfortunately humans are not quantitative and don't measure things precisely.
A compromise solution would be to call user_save() in a shutdown function which keep the API compatibility while improving the user experience. This pattern is used by modules such as workbench moderation.
I have implemented something very similar in one of my own modules, views usage audit. If you are happy with this solution I am more than willing to write a patch.