Webapp2_extras.auth with backend 'datastore' not working properly - google-app-engine

Setting the session backend to datastore will not work properly because every call to auth.get_user_by_session() causes a put() to the datastore of the session entity even if you didn't modify the session values.
That's caused because a call to auth.get_user_by_session() makes a call to auth.get_session_data(pop=True). Pop=True will indeed pop the _user from the session dict object (SessionDict from the webapp2_extras.sessions) which causes the session to get the attribute modified to True.
Therefore when the handler completes it's tasks a call to save_session in the handler dispatch method will check if the session was modified and if True (which always is TRUE!!!) will do the put to the datastore.
So, for every request the session is put to the datastore because obviously you're calling auth.get_user_by_session() on each request.
I don't know how to handle this but I have already open a issue in the google code project page (which seems abandoned):
https://code.google.com/p/webapp-improved/issues/detail?id=100&sort=-id
Can anyone help me understand what's happening here? and how can I solve this?

So, if we inspect the following code from webapp2_extras.auth you the get the following:
def get_user_by_session(self, save_session=True):
"""Returns a user based on the current session.
:param save_session:
If True, saves the user in the session if authentication succeeds.
:returns:
A user dict or None.
"""
if self._user is None:
data = self.get_session_data(pop=True)
if not data:
self._user = _anon
else:
self._user = self.get_user_by_token(
user_id=data['user_id'], token=data['token'],
token_ts=data['token_ts'], cache=data,
cache_ts=data['cache_ts'], remember=data['remember'],
save_session=save_session)
return self._user_or_none()
After that lets inspect the get_session_data()
def get_session_data(self, pop=False):
"""Returns the session data as a dictionary.
:param pop:
If True, removes the session.
:returns:
A deserialized session, or None.
"""
func = self.session.pop if pop else self.session.get
rv = func('_user', None)
if rv is not None:
data = self.store.deserialize_session(rv)
if data:
return data
elif not pop:
self.session.pop('_user', None)
return None
I do not see where why it pops in case you have a user in the session? it points to the user_or_none which unsets the session if the user is None.
Amazing blog post on built in authorization in webapp2:
http://blog.abahgat.com/2013/01/07/user-authentication-with-webapp2-on-google-app-engine/

Related

how does get_current_user work

I'm really confused how Google App Engine's User's get_current_user() works. I've looked around the internet at a bunch of different guides and tutorials about login and authentication, and many of them mention similar methods.
If there are a million users logged in to my application at the same time, how can that method possibly work? Does each user get their own instance of the server? How does the server know which client it is talking to?
It doesn't make sense to me at all.
When logging in (by clicking on the URL generated by create_login_url()) a cookie containing user identifying information is prepared and pushed on the client side, then used in subsequent requests until the user logs out or the cookie expires. Calling get_current_user() simply checks the cookie existance/information and responds accordingly.
On the development server the cookie is named dev_appserver_login. I can no longer check the cookie name on GAE as I switched away from the Users API.
The actual handling of the cookie seems to happen somewhere on the Users service backend, for example, by looking at the google/appengine/api/users.py file in the python SDK:
def create_login_url(dest_url=None, _auth_domain=None,
federated_identity=None):
...
req = user_service_pb.CreateLoginURLRequest()
resp = user_service_pb.CreateLoginURLResponse()
try:
apiproxy_stub_map.MakeSyncCall('user', 'CreateLoginURL', req, resp)
...
The end point (at least for the development server) seems to somehow land somewhere in google/appengine/tools/appengine_rpc.py, for example:
#staticmethod
def _CreateDevAppServerCookieData(email, admin):
"""Creates cookie payload data.
Args:
email: The user's email address.
admin: True if the user is an admin; False otherwise.
Returns:
String containing the cookie payload.
"""
if email:
user_id_digest = hashlib.md5(email.lower()).digest()
user_id = "1" + "".join(["%02d" % ord(x) for x in user_id_digest])[:20]
else:
user_id = ""
return "%s:%s:%s" % (email, bool(admin), user_id)
def _DevAppServerAuthenticate(self):
"""Authenticates the user on the dev_appserver."""
credentials = self.auth_function()
value = self._CreateDevAppServerCookieData(credentials[0], True)
self.extra_headers["Cookie"] = ('dev_appserver_login="%s"; Path=/;' % value)

Using webapp2 session as namespace name. How?

I have an application which is school based. Each tenant is a different school and to access the application all users for each school have the same password.
Alongside this each school user has to have a google email if they want access to the application. So the application first checks they are a google user, checks wether they are a school user and finally checks that their google email is in the school user list before they are allowed access to any page.
The school user part is using session data from webapp2 sessions to ensure each request they have appropriate access
class Handler(webapp2.RequestHandler):
def dispatch(self):
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self)
finally:
# Save all sessions.
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def session(self):
# Returns a session using the default cookie key.
return self.session_store.get_session()
When a user logins I check the password then create a session which checks their password / user combination every request.
def check_u(self):
try:
uid = self.session.get('user')
parent = self.session.get('school-id')
udl = m.models.User.by_id(int(uid),parent)
if uid and udl:
return udl
else:
return False
except (TypeError,AttributeError):
return False
A parent datastore entity for each different school is used called MetaSchool which I have been currently using to ensure that there is no data leak across schools. Each datastore entry uses this parent session key as a way of setting the datastore entry with MetaSchool as parent then using this session key again to read back this data.
This method works but is onerous. I would like to use namespace as a way of separating the data but would like to use the Metaschool id as the name.
def namespace_manager_default_namespace_for_request():
### Here I need to get ------ parent = self.session.get('school-id')
### use this session to gain the MetaSchool key id
### Set this as the namespace name
Basically trying to emulate from the docs the below scenario
from google.appengine.api import users
def namespace_manager_default_namespace_for_request():
# assumes the user is logged in.
return users.get_current_user().user_id()
I am having difficulty getting the session data from Handler object???
Any thoughts
This is what I came up with.
from google.appengine.api import namespace_manager
from webapp2_extras import sessions
def namespace_manager_default_namespace_for_request():
session = sessions.get_store()
s = session.get_session()
name = s.get('key')
if name:
return name
else:
return namespace_manager.set_namespace('string')

(O)Auth with ExtJS

today i tried to get django-piston and ExtJS working. I used the ExtJS restful example and the piston example to create a little restful webapp. Everything works fine except the authentication.
Whats the best way to get Basic/Digest/OAuth authentication working with ExtJS?
Atm I'm not sure where to set the Username/Password.
Thanks
If you want to use piston with ExtJS, I would suggest writing an anonymous handler that checks the user is logged in via standard auth.
Try this:
class AnonymousUserProfileHandler(BaseHandler):
fields = ('title', 'url', 'affiliation')
model = UserProfile
def read(self, request, nickname):
profile = UserProfile.objects.get(nickname=nickname)
if request.user == profile.user:
return profile
class UserProfileHandler(BaseHandler):
anonymous = AnonymousUserProfileHandler
allowed_methods = ('GET')
fields = ('title', 'url', 'affiliation')
model = UserProfile
def read(self, request, nickname):
profile = UserProfile.objects.get(nickname=nickname)
return profile
In this example, when UserProfileHandler is called, without any authorization, it delegates to the anonymous handler. The anonymous handler checks whether the user is logged in via the usual request.user mode. If there is a valid user, it returns their profile object. You would then, obviously, mark the view calling this as requiring login.
The point is: when extJS makes its JSON call, it will send authentication data via the usual cookie. If you use an "anonymous" handler in Piston, but manually check the user is logged in before returning the data, then you essentially use traditional auth for your own site.

Detecting first time login of user into application (Google Appengine)

My app requires users to login using their google account.
I have this set in my App.yamp file:
url: /user/.*
script: user.py
login: required
Now when any user tries to access files under /user/secret.py he will need to authenticate via google, which will redirect the user back to /user/secret.py after successful authentication. Now the problem I am facing is when the user is redirected back to the app, I cannot be sure if this is the first time the user has logged in or is it a regular user to my site who has come back again from just the user object which google passes using users.get_current_user() .
I thus need to maintain state in the datastore to check if the user already exists or not everytime. If he does not exist i need to create a new entry with other application specific settings.
My question is: Is there some easier way to handle this? without having to query the datastore to figure if this is a first time user or a regular one?
No, Google doesn't keep track of if a user has logged in to your app before. Since you presumably need to store some sort of state against the user, the simplest way is to try and retrieve the user's record from the datastore. If they don't have one, you can send them to the registration screen to gather this information. You can use memcache to cache a user's information and avoid extra datastore round-trips.
I tend to use my own user and session manangement
For my web handlers I will attach a decorator called session and one called authorize. The session decorator will attach a session to every request, and the authorize decorator will make sure that the user is authorised
(A word of caution, the authorize decorator is specific to how I develop my applications - the username being the first parameter in most requests)
So for example a web handler may look like:
class UserProfile(webapp.RequestHandler):
#session
#authorize
def get(self, user):
# Do some funky stuff
# The session is attached to the self object.
someObjectAttachedToSession = self.SessionObj.SomeStuff
self.response.out.write("hello %s" % user)
In the above code, the session decorator attaches some session stuff that I need based on the cookies that are present on the request. The authorize header will make sure that the user can only access the page if the session is the correct one.
The decorators code are below:
import functools
from model import Session
import logging
def authorize(redirectTo = "/"):
def factory(method):
'Ensures that when an auth cookie is presented to the request that is is valid'
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
#Get the session parameters
auth_id = self.request.cookies.get('auth_id', '')
session_id = self.request.cookies.get('session_id', '')
#Check the db for the session
session = Session.GetSession(session_id, auth_id)
if session is None:
self.redirect(redirectTo)
return
else:
if session.settings is None:
self.redirect(redirectTo)
return
username = session.settings.key().name()
if len(args) > 0:
if username != args[0]:
# The user is allowed to view this page.
self.redirect(redirectTo)
return
result = method(self, *args, **kwargs)
return result
return wrapper
return factory
def session(method):
'Ensures that the sessions object (if it exists) is attached to the request.'
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
#Get the session parameters
auth_id = self.request.cookies.get('auth_id', '')
session_id = self.request.cookies.get('session_id', '')
#Check the db for the session
session = Session.GetSession(session_id, auth_id)
if session is None:
session = Session()
session.session_id = Session.MakeId()
session.auth_token = Session.MakeId()
session.put()
# Attach the session to the method
self.SessionObj = session
#Call the handler.
result = method(self, *args, **kwargs)
self.response.headers.add_header('Set-Cookie', 'auth_id=%s; path=/; HttpOnly' % str(session.auth_token))
self.response.headers.add_header('Set-Cookie', 'session_id=%s; path=/; HttpOnly' % str(session.session_id))
return result
return wrapper
def redirect(method, redirect = "/user/"):
'When a known user is logged in redirect them to their home page'
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
try:
if self.SessionObj is not None:
if self.SessionObj.settings is not None:
# Check that the session is correct
username = self.SessionObj.settings.key().name()
self.redirect(redirect + username)
return
except:
pass
return method(self, *args, **kwargs)
return wrapper
Can you not just set a Cookie the first time the user logs in and check for this? If they're a new user it won't be there and but if they're an old user it will be. It's not 100% accurate since some users might clear their cookies but it might do depending on what it is you want to achieve.
If you're using Django in your application managing Cookies is pretty straightforward.
I agree that managing your own authenticated users is the best way to approach this problem. Depending on your application scope obviously but at the very least an AuthUser(Model) class that contains the UserProperty for the users that have logged in with your account.
...
class AuthUser(db.Model):
user = UserProperty(required=True)
...
Then when a user logs in just
...
user = users.get_current_user()
user_exists = AuthUser.gql('where user = :1', user) # or easy check db.GqlQuery("select __key__ from AuthUser where user = :1", user)
if user_exists:
# do user has been before stuff
else:
# do first time user stuff
...
Alternately a super easy way to do this is have a Model for your site that has a ListProperty(users.User) and then you can easily check the list to see if the user has been into your app before.
...
class SiteStuff(db.Model):
auth_users = ListProperty(users.User)
...
and when they log in: check if they are in the list; if not, you add them to the list, put() it and do whatever you need to do for first time users. If you find them in there then do the other stuff.

Django users and authentication from external source

I have a Django app that gets it's data completely from an external source (queried via HTTP). That is, I don't have the option for a local database. Session data is stored in the cache (on my development server I use a SQLite database, so that is no error source). I'm using bleeding edge Django 1.1svn.
Enter the problem: I want to use Django's own authentication system for the users.
It seems quite simple to write my own Authentication Backend, but always just under the condition that you have a local database where to save the users. Without database my main problem is persistence.
I tried it with the following (assume that datasource.get() is a function that returns some kind of dict):
class ModelBackend (object):
"""Login backend."""
def authenticate (self, username=None, password=None):
"""Check, if a given user/password combination is valid"""
data = datasource.get ('login', username, password)
if data and data['ok']:
return MyUser (username=username)
else:
raise TypeError
return None
def get_user (self, username):
"""get data about a specific user"""
try:
data = datasource.get ('userdata', username)
if data and data['ok']:
return data.user
except:
pass
return None
class MyUser (User):
"""Django user who isn't saved in DB"""
def save (self):
return None
But the intentionally missing save() method on MyUser seems to break the session storage of a login.
How should MyUser look like without a local database?
OK, it's much more complicated than I thought. First, start with http://docs.djangoproject.com/en/dev/howto/auth-remote-user/, but you'll need to extend it with your own backend and user.
from django.contrib.auth.backends import RemoteUserBackend
class MyRemoteUserBackend (RemoteUserBackend):
# Create a User object if not already in the database?
create_unknown_user = False
def get_user (self, user_id):
user = somehow_create_an_instance_of (MyUser, user_id)
return user
def authenticate (self, **credentials):
check_credentials ()
user = somehow_create_an_instance_of (MyUser, credentials)
return user
Then the user:
from django.contrib.auth.models import User
class MyUser (User):
def save (self):
"""saving to DB disabled"""
pass
objects = None # we cannot really use this w/o local DB
username = "" # and all the other properties likewise.
# They're defined as model.CharField or similar,
# and we can't allow that
def get_group_permissions (self):
"""If you don't make your own permissions module,
the default also will use the DB. Throw it away"""
return [] # likewise with the other permission defs
def get_and_delete_messages (self):
"""Messages are stored in the DB. Darn!"""
return []
Phew! Django really isn't designed for usage without a database...
Rather than overwriting the save method, you may also disconnect the signal that invokes it. This is what I do in some apps which have read-only access to the user database.
# models.py (for instance)
from django.contrib.auth.models import update_last_login, user_logged_in
user_logged_in.disconnect(update_last_login)
grepping the source showed, that the only place user.save() is actually called (except for user creation and password management code, which you don't need to use at all) is django.contrib.auth.login(), to update user.last_login value.
# TODO: It would be nice to support different login methods, like signed cookies.
user.last_login = datetime.datetime.now()
user.save()
If you don't want user data to rest in DB, try adding dummy save() method. If I'm right, it should work.
def save(self, *args, **kwargs):
pass
Of course, because you have no persistence at all, you should consider caching datasource.get results, otherwise in worst case you may end up querying data again and again on every single logged in user's hit.

Resources