Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Pyramid.security: Is getting user info from a database with unauthenticated_userid(request) really secure?

I'm trying to make an accesible cache of user data using Pyramid doc's "Making A “User Object” Available as a Request Attribute" example.

They're using this code to return a user object to set_request_property:

from pyramid.security import unauthenticated_userid

def get_user(request):
    # the below line is just an example, use your own method of
    # accessing a database connection here (this could even be another
    # request property such as request.db, implemented using this same
    # pattern).
    dbconn = request.registry.settings['dbconn']
    userid = unauthenticated_userid(request)
    if userid is not None:
        # this should return None if the user doesn't exist
        # in the database
        return dbconn['users'].query({'id':userid})

I don't understand why they're using unauthenticated_userid(request) to lookup user info from the database...isn't that insecure? That means that user might not be logged in, so why are you using that ID to get there private info from the database?

Shouldn't

    userid = authenticated_userid(request)

be used instead to make sure the user is logged in? What's the advantage of using unauthenticated_userid(request)? Please help me understand what's going on here.

like image 697
zakdances Avatar asked Sep 22 '12 20:09

zakdances


3 Answers

The unauthenticated_userid call is a cheaper call; it looks up the user id from the request without going through the whole authentication process again.

The key concept there is the word again. You should only use the method in views that have already been authorized. In other words, by the time you reach code that uses unauthenticated_userid you've already verified the user, and specifically do not want to do this again for this particular call.

Authenticating users against a backend persistent storage can be expensive, especially if such a storage doesn't support caching. The unauthenticated_userid API method is an optimization where the request is basically your userid cache.

like image 190
Martijn Pieters Avatar answered Oct 05 '22 21:10

Martijn Pieters


This is a late reply but it was linked as a source of confusion for some users of Pyramid.

The accepted answer here is not the actual reason that unauthenticated_userid is used for request.user. It has nothing to do with performance.

The reason that it uses unauthenticated_userid is because it makes it easier to reuse the authentication policy between applications with smaller modifications required. Your application needs a "source of truth" for whether the user is allowed to be considered authenticated and usually the policy's internal logic is not enough to make this determination. A valid cookie is nice, but you usually want to verify it with your backend before trusting it. Great, so where do we put that logic? Well unauthenticated_userid doesn't make sense because it is the reusable part of the policy that focuses specifically on parsing the request headers. You could put it into authenticated_userid but this method is not the one you normally care about in your application. You normally use request.user in your apps (rarely do you probably care about request.authenticated_userid directly) and lastly the request.user is a superset of functionality - it provides an entire user object, not just an id. It would be silly to verify the id without verifying the entire object in most cases. We can only have one "source of truth" and so the recipe declares it to be request.user. The groupfinder (and thus authenticated_userid) can now depend on request.user and trust that what it gets back from there has been properly verified with the backend. Also request.user is already reified and thus speeds up subsequent calls to request.authenticated_userid naturally.

like image 24
Michael Merickel Avatar answered Oct 05 '22 20:10

Michael Merickel


Looks like Martijn Pieters is right.

My micro benchmark to test this (in my project I use Redis as DB for users and everything else):

print ('start test')
t1 = time()
authenticated_userid(self.request)
print ('authenticated: ' + str(time()-t1))
t1 = time()
unauthenticated_userid(self.request)
print ('unauthenticated: ' + str(time()-t1))
print ('test_stop')

Results:

start test
REDIS AUTH! # <-- actually this is query to groups finder in Redis
authenticated: 0.00032901763916
unauthenticated: 7.31945037842e-05
test_stop

It was tested for few times, results are constant :) Do you think I should add Martijn's answer to that article in Pyramid docs to make things more 'clear'? :)

like image 30
Sapphire Avatar answered Oct 05 '22 21:10

Sapphire