I am extending the dispatch method as specified in Webapp2 Sessions documentation:
https://webapp2.readthedocs.io/en/latest/_modules/webapp2_extras/sessions.html#SessionStore
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()
I want to get all the active sessions of a particular user for some purpose. How do I do that?
Try the UserToken model.
from webapp2_extras.appengine.auth.models import UserToken
def all_sessions_for_user(user, _limit=100):
return UserToken.query(UserToken.user == str(user.key.id()).fetch(limit=_limit)
EDIT - OPTION 2
Then, I believe the only way is to define your own session_store backend, most likely using the datastore. There is a datastore backend that webapp2 provides (backend options are securecookie, datastore, and memcache), but it doesn't have a user field.
So you would need to subclass both Session and DatastoreSessionFactory to add it:
from webapp2_extras import auth
from webapp2_extras.appengine.sessions_ndb import Session, DatastoreSessionFactory
class MySession(Session):
user_id = ndb.KeyProperty(User)
class MyDatastoreSessionFactory(DatastoreSessionFactory):
session_model = MySession
def save_session(self, response):
"""extract user_id from self.session.data and pass it to self.session_model constructor"""
if self.session is None or not self.session.modified:
return
deserialized_session = auth.get_auth().store.deserialize_session(self.session.data)
self.session_model(id=self.sid, data=dict(self.session), user_id=deserialized_session['user_id'])._put()
self.session_store.save_secure_cookie(response, self.name, {'_sid': self.sid}, **self.session_args)
And then when you call get_session(), you just need to specify your new backend factory:
self.session_store.get_session(factory=MyDatastoreSessionFactory')
Related
I am trying to implement a 'remember me' feature on the login page.
This is the logic I have in mind: Store sessions in the datastore,
pass the session id/key to the client so that next time the user visits
the site, i get information from the datastore with the key the client has
I was about to do something like this:
class Session(ndb.Model):
username = ndb.StringProperty()
email = ndb.StringProperty()
if request.get('rememberme'):
session = Session()
session.email = 'john#doe.com'
session.username = 'jon snow'
key = session.put()
# send `key` back to client and store in a cookie, so
# when client visits the site again, get the session
# values from the datastore
self.response.write(key.id())
But I'm using this snippet from the docs to handle my sessions:
class BaseHandler(webapp2.RequestHandler):
def dispatch(self):
self.session_store = sessions.get_store(request=self.request)
try:
webapp2.RequestHandler.dispatch(self)
finally:
self.session_store.save_sessions(self.response)
#webapp2.cached_property
def session(self):
return self.session_store.get_session(name='foo', backend='datastore')
This too ,inserts data into the datastore under the 'Session' Kind.
So it feels like I'd be doing redudant work if I were to manually store sessions in the datastore as well.
When using webapp2's session with datastore as the backend, how can I know that session's id/key?
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')
I have an endpoint method that requires a user object. Would I do the following? It seems a bit strange since I could get user using endpoints.get_current_user()
#endpoints.method(FriendListRequest, FriendListResponse,
path='scores', http_method='POST',
name='scores.list')
def friend_list(self, request):
# work would go here
Then the FriendListRequest would be
class FriendListRequest(messages.Message):
user_object = messages.Field(1, required=True)
The reason I need the User object is because I must use the User email to query and find the friends of said user.
To securely authenticate a user, Cloud Endpoints provides simple OAuth 2.0 support. Instead of passing a user object (insecure) in with the request, the request message can be a VoidMessage and you can rely on the Authorization header, which contains the OAuth 2.0 token for the user.
To actually get the current user, you will call endpoints.get_current_user(); this will require adding allowed_client_ids and/or audiences in either the #endpoints.method decorator or the the #endpoints.api method. See the docs for more info.
For example, on the API:
#endpoints.api(name='myapi', ...,
allowed_client_ids=[MY_CLIENT_ID])
class MyApi(...):
#endpoints.method(message_types.VoidMessage, FriendListResponse,
path='scores', http_method='POST',
name='scores.list')
def friend_list(self, request):
user = endpoints.get_current_user()
# work would go here
or, on the method:
#endpoints.method(message_types.VoidMessage, FriendListResponse,
path='scores', http_method='POST',
name='scores.list',
allowed_client_ids=[MY_CLIENT_ID])
def friend_list(self, request):
user = endpoints.get_current_user()
# work would go here
Create a user object using users and pass the same
A User instance can be also constructed from an email address:
from google.appengine.api import users
user = users.User("A***.j***#gmail.com")
I tried to implement GAE's webapp2 session, but there seems very little documentation about it. According to http://webapp-improved.appspot.com/api/webapp2_extras/sessions.html, my steps are as follows:
1.Configure and add config to the main application:
config = {}
config['webapp2_extras.sessions'] = {
'secret_key': 'my_secret_key',
}
app = webapp2.WSGIApplication([...], config=config)
2.Create session in the login handler
# Delete existent session
--> not mention in the tutorial
# member is found
self.session_store = sessions.get_store(request=handler.request)
self.session['account'] = member.account
3.Check if a session exists at various locations in my program
if self.session['account']:
# Session exists
4.Delete session when user logs out
--> not mentioned in the tutorial
My questions:
I got error message " ... object has no attribute 'session'" during the session creation process (Step 2)
How do I delete a session in steps 2 and 4?
Is the overall session management process correct?
Thanks.
Here is an example of the handler and how to use webapp2 extra sessions
main.py with the BaseHandler and a MainHandler
import webapp2
from webapp2_extras import sessions
class BaseHandler(webapp2.RequestHandler): # taken from the webapp2 extrta session example
def dispatch(self): # override dispatch
# Get a session store for this request.
self.session_store = sessions.get_store(request=self.request)
try:
# Dispatch the request.
webapp2.RequestHandler.dispatch(self) # dispatch the main handler
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()
class YourMainHandler(BaseHandler):
def get(self):
....
self.session['foo'] = 'bar'
def post(self):
foo = self.session.get('foo')
And if you have a seperate login.py :
.... other imports
import main
class Login(main.BaseHandler):
def get(self):
....
self.session['foo'] = 'bar'
def post(self):
foo = self.session.get('foo')
This may not be a direct answer to the question, but it is a solution I found using gaesessions instead of GAE's webapp2 session and I would like to share with everybody. Here we go:
Download gaesessions from https://github.com/dound/gae-sessions by clicking "Download ZIP" button. The downloaded file is "gae-sessions-master.zip".
Unzip the file (a directory "gae-sessions-master" will be created), and copy the directory "gaessions" to the root directory of your application (i.e., where "app.yaml" is)
Create a file called "appengine_config.py" in the root directory, with the following content (copied form https://github.com/dound/gae-sessions/tree/master/demo):
from gaesessions import SessionMiddleware
# Original comments deleted ...
# Create a random string for COOKIE_KDY and the string has to
# be permanent. "os.urandom(64)" function may be used but do
# not use it *dynamically*.
# For me, I just randomly generate a string of length 64
# and paste it here, such as the following:
COOKIE_KEY = 'ppb52adekdhD25dqpbKu39dDKsd.....'
def webapp_add_wsgi_middleware(app):
from google.appengine.ext.appstats import recording
app = SessionMiddleware(app, cookie_key=COOKIE_KEY)
app = recording.appstats_wsgi_middleware(app)
return app
Create a session when a user logs in (variable account is the user's account):
from gaesessions import get_current_session
session = get_current_session()
if session.is_active():
session.terminate()
# start a session for the user (old one was terminated)
session['account'] = account
Check if the user's session exists, if yes, return user's account:
from gaesessions import get_current_session
def checkSession():
session = get_current_session()
if session.is_active():
return session['account']
return False
Delete the session when the user logs out:
def logout():
session = get_current_session()
if session.is_active():
session.terminate()
Finally, you may create a cron job to clean expired sessions periodically:
cron.yaml:
- description: daily session cleanup
url: /clean_up_sessions
schedule: every day 3:00
timezone: ... (Your time zone)
Function:
from gaesessions import delete_expired_sessions
class clean_up_sessions(webapp2.RequestHandler):
def get(self):
while not delete_expired_sessions():
pass
Hope this helps.
In your RequestHandler override dispatch:
from webapp2_extras import sessions
def dispatch(self):
self.session_store = sessions.get_store(request=self.request)
try:
webapp2.RequestHandler.dispatch(self)
finally:
self.session_store.save_sessions(self.response)
and make a webapp2.cached_property called session:
#webapp2.cached_property
def session(self):
return self.session_store.get_session(backend="<whatever you want here>")
When you want to access session values, you do self.session[<key>]
When a user logs in, you can call either:
self.auth.get_user_by_password(auth_id, password, remember=True,
save_session=True)
which will take care of getting rid of the old session and creating the new one for you, or:
self.auth.set_session(self.auth.store.user_to_dict(self.user), remember=True)
As far as logging out, all you should need to call is:
self.auth.unset_session()
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.