Feature request: get user by session key

99 views
Skip to first unread message

andreymal

unread,
Jul 26, 2019, 10:34:52 AM7/26/19
to django-d...@googlegroups.com
(Hello, the Django documentation says that I should send feature
requests to the django-developers mailing list, so I send it here.)

I have a need to get a user object using a known session key without an
HTTP request. After reading the Django code, I did not find a clean way
to do this.

Django has the "get_user" function that requires a request object. But
it actually uses only "request.session" and nothing else. So I suggest
refactoring it something like this:

#
https://github.com/django/django/blob/master/django/contrib/auth/__init__.py
def _get_user_session_key(session):
    return get_user_model()._meta.pk.to_python(session[SESSION_KEY])

def get_user(request):
    return get_user_by_session(request.session)

def get_user_by_session_key(session_key):
    SessionStore = import_module(settings.SESSION_ENGINE).SessionStore
    return get_user_by_session(SessionStore(session_key))

def get_user_by_session(session):
    from .models import AnonymousUser
    user = None
    try:
        user_id = _get_user_session_key(session)
        backend_path = session[BACKEND_SESSION_KEY]
    except KeyError:
        pass
    else:
        ... # the rest of the code
    return user or AnonymousUser()

Note that Django Channels has similar code so the "get_user_by_session"
function can help to deduplicate it:
https://github.com/django/channels/tree/master/channels/auth.py#L23

Adam Johnson

unread,
Jul 26, 2019, 12:40:22 PM7/26/19
to django-d...@googlegroups.com
Hey,

Welcome to the mailing list. This is probably a little low-level as a feature request here. I think it is better submitted as a ticket as per https://docs.djangoproject.com/en/dev/internals/contributing/bugs-and-features/ :) Where on the docs did you read about django-developers?

As for your suggested feature - could you talk about your use case a little more? If Django is going to support sessions not attached to requests, it's likely this isn't the only thing that would need changing. And as you've seen in the source, the function you want right now is only one line (get_user_session_key).

Thanks,

Adam

--
You received this message because you are subscribed to the Google Groups "Django developers  (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/858abfe1-42b9-185b-e69c-c23d6058e762%40andreymal.org.


--
Adam

Jani Tiainen

unread,
Jul 26, 2019, 1:26:49 PM7/26/19
to django-d...@googlegroups.com
Hi.

I have few times had a need to get user by session. For debugging purposes.

Also django-extensions do have management command to return user from session id.

But I really can't imagine any usecase where just session would make sense...

andreymal

unread,
Jul 26, 2019, 2:02:57 PM7/26/19
to django-d...@googlegroups.com

The page you referred to says:

>First request the feature on the django-developers list, not in the ticket tracker.

That's why I'm here ¯\_(ツ)_/¯

About my case — in general, I want to make a websocket server. Django Channels is too complicated and I don't want to use it for my case; instead, I want to make a very stupid and primitive server (without consumers, routers, channel layers and other scary words; less than 0.5k lines of code). But I still need to authenticate the user, and I want to do exactly what Channels does — get the user model instance for current websocket scope.

Now, to prevent code duplication, I have to do something like this:

def get_session(self):
    SessionStore = importlib.import_module(settings.SESSION_ENGINE).SessionStore
    # self.session_key is loaded from websocket cookies
    return SessionStore(self.session_key)

def get_user_model(self):
    from django.contrib.auth import get_user
    from django.http.request import HttpRequest
    request = HttpRequest()
    request.session = self.get_session()
    return get_user(request)

But this is obviously ugly. But creating a copy of the "get_user" function and modifying it to fit my needs is even worse.

The "get_user_by_session_key" function I propose can dramatically simplify this code and increase reliability. The same applies to Channels that can get rid of its own copy of the "get_user" function.

I expect you'll say "just use Channels and don't be an idiot", but I think this small refactoring will be good anyway, even for Channels. I don't quite understand what you mean by saying "isn't the only thing that would need changing"; as of today, I have not noticed any problems when working with the session without a real request. (I know that it’s not very safe to work with the ORM in async code, but don't worry, I took care of this.)

26.07.2019 19:39, Adam Johnson пишет:

Adam Johnson

unread,
Aug 31, 2019, 6:07:46 AM8/31/19
to django-d...@googlegroups.com
Hey Andrey,

Sorry for slow reply

>First request the feature on the django-developers list, not in the ticket tracker

My bad - I haven't come up with a new feature myself in a while! 

I expect you'll say "just use Channels and don't be an idiot",

Absolutely not - I trust your technical judgment in making your own minimal websocket server!
 
But this is obviously ugly

I wouldn't say your solution is obviously ugly. In fact, I think it's quite good.

All the contrib.auth functions are assuming that there's a request with the session attached, and that's what you're providing. You've done a good job adapting it for the new environment you're running it under.

By "isn't the only thing that would need changing" I mean that for contrib.auth to consistently support request-free sessions, it would probably need more functions duplicated/reworked to support passing in just a session instead of a request. For example Channels is duplicating:
  • login()
  • logout()
  • get_user()
But I also think these would need work:
  • update_session_auth_hash()
  • The backends themselves - they take a request for authentication purposes
  • The signals or even users' signal handlers - they send/receive the request object
And that's just in contrib.auth - if we want to properly support request-free sessions everywhere, it would require work in many more parts of Django.

Overall, I think your current approach of adapting to the API's by providing a "fake" request is a good one.

Granted Channels has more duplication than you, but I think as Django itself moves towards async compat ( https://github.com/django/deps/blob/master/accepted/0009-async.rst ) you'll see Andrew reduce some of that gap.

Thanks,

Adam



--
Adam
Reply all
Reply to author
Forward
0 new messages