Skip to content

Instantly share code, notes, and snippets.

@joerussbowman
Last active December 29, 2015 14:49
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save joerussbowman/7686878 to your computer and use it in GitHub Desktop.
Save joerussbowman/7686878 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
#
# Copyright 2013 Joseph Bowman
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import motor
import functools
import uuid
import datetime
import settings
import time
import logging
import bson
from tornado.web import RequestHandler
class MoSession(object): [174/207]
"""
MoSession class, used to manage persistence across multiple requests. Uses
a mongodb backend and Cookies. This library is designed for use with
Tornado.
Built on top of Motor.
The decorator is written to be completely asynchronous and not block.
The session is added as session property to your request handler, ie:
self.session. It can be manipulated as your would any dictionary object.
Included with the library is a settings file, configured for default
permissions. Some of the more advanced tuning you can do is with token
expiration. In order to create some additional security for sessions used
in a non-ssl environment, the token stored in the browser rotates. If you
are using ssl, or more interested in performance than security you can set
SESSION_TOKEN_TTL to an extremely high number to avoid writes.
Note: In an effort increate performance, all data writes are delayed until
after the request method has completed. However, security token updates
are saved as they happen.
"""
def __init__(self, req_obj,
cookie_path=settings.session["DEFAULT_COOKIE_PATH"],
cookie_name=settings.session["COOKIE_NAME"],
set_cookie_expires=settings.session["SET_COOKIE_EXPIRES"],
session_token_ttl=settings.session["SESSION_TOKEN_TTL"],
session_expire_time=settings.session["SESSION_EXPIRE_TIME"],
mongo_collection=settings.session["MONGO_COLLECTION"],
db=None,
callback=None): [142/207]
"""
__init__ loads the session, checking the browser for a valid session
token. It will either validate the session and/or create a new one
if necessary.
The db object should be a mongodb database, not collection. The
collection value is set by the settings for the library. See
settings.py for more information.
If you already have a db attribute on the request or application
objects then there is no need to pass it. Sessions will automatically
check those objects for a valid database object to use.
"""
logging.error("starting init")
self.req_obj = req_obj
self.cookie_path = cookie_path
self.cookie_name = cookie_name
self.session_token_ttl = session_token_ttl
self.session_expire_time = session_expire_time
self.callback = callback
if db:
self.db = db[mongo_collection]
elif hasattr(self.req_obj, "db"):
self.db = self.req_obj.db[mongo_collection]
elif hasattr(self.req_obj.application, "db"):
self.db = self.req_obj.application.db[mongo_collection]
else:
raise ValueError("Invalid value for db")
self.new_session = True
self.do_put = False
self.do_save = False [110/207]
self.do_delete = False
self.cookie = self.req_obj.get_secure_cookie(cookie_name)
if self.cookie:
logging.error("got cookie %s" % self.cookie)
(self.token, _id) = self.cookie.split("@")
logging.error("looking up session")
self.session = self.db.find_one({"_id":
bson.ObjectId(_id)}, callback=self._validate_cookie)
else:
logging.error("no cookie")
self._new_session()
def _new_session(self):
logging.error("starting new session")
self.session = {"_id": bson.ObjectId(),
"tokens": [str(uuid.uuid4())],
"last_token_update": datetime.datetime.utcnow(),
"data": {},
}
self._put()
def _validate_cookie(self, response, error):
logging.error("validating cookie")
if response:
self.session = response
if self.token in self.session["tokens"]:
self.new_session = False
if self.new_session:
self._new_session()
else:
duration = datetime.timedelta(seconds=self.session_token_ttl) [78/207]
session_age_limit = datetime.datetime.utcnow() - duration
if self.session['last_token_update'] < session_age_limit:
self.token = str(uuid.uuid4())
if len(self.session['tokens']) > 2:
self.session['tokens'].pop(0)
self.session['tokens'].insert(0,self.token)
self.session["last_token_update"] = datetime.datetime.utcnow()
self.do_put = True
if self.do_put:
self._put()
else:
self._handle_response()
def _put(self):
logging.error("storing id")
if self.session.get("_id"):
self.db.update({"_id": self.session["_id"]}, {"$set": {"data":
self.session["data"], "tokens": self.session["tokens"],
"last_token_update": self.session["last_token_update"]}},
upsert=True,
callback=self._handle_response)
else:
self.db.save(self.session, callback=self._handle_response)
def _handle_response(self, *args, **kwargs):
logging.error("setting cookie and running request handler")
cookie = "%s@%s" % (self.session["tokens"][0], self.session["_id"])
self.req_obj.set_secure_cookie(name = self.cookie_name, value =
cookie, path = self.cookie_path)
self.callback(self.req_obj)
[46/207]
def get_token(self):
return self.cookie
def get_id(self):
return self.session.get("_id")
def delete(self):
self.session['tokens'] = []
self.do_delete = True
return True
def has_key(self, keyname):
return self.__contains__(keyname)
def get(self, key, default=None):
if self.has_key(key):
return self[key]
else:
return default
def __delitem__(self, key):
del self.session["data"][key]
self.do_save = True
return True
def __getitem__(self, key):
return self.session["data"][key]
def __setitem__(self, key, val):
self.session["data"][key] = val
self.do_save = True [14/207]
return True
def __len__(self):
return len(self.session["data"])
def __contains__(self, key):
return self.session["data"].has_key(key)
def __iter__(self):
for key in self.session["data"]:
yield key
def __str__(self):
return u"{%s}" % ', '.join(['"%s" = "%s"' % (k, self.session["data"][k]) for k in self.session["data"]])
def _pass(self, response, error):
pass
def mosession(method):
@functools.wraps(method)
def wrapper(self, *args, **kwargs):
def on_finish(self, *args, **kwargs):
"""
This is a monkey patch finish which will save or delete
session data at the end of a request.
"""
super(self.__class__, self).on_finish(*args, **kwargs)
logging.error("doing finish")
if self.session.do_save:
db.update({"_id": self.session.session["_id"]}, {"$set": {"data":
self.session.session["data"]}},
callback=self.session._pass)
if self.session.do_delete:
self.session.db.remove({"_id": self.session.session["_id"]},
callback=self.session._pass)
logging.error("starting")
self.on_finish = functools.partial(on_finish, self)
self.session = MoSession(self, callback=method)
#method(self, *args, **kwargs)
return wrapper
class DealerCreatedHandler(BaseHandler):
@mosession
def get(self):
logging.error("I am getting run")
confirm = self.get_argument("confirm")
account = yield motor.Op(
db.accounts.find_one, {"confirm": confirm}
)
if not account:
self.render("error.html", error="Could not find account for that code.")
else:
self.session["account"] = account["_id"]
self.render("dealerCreated.html")
#!/usr/bin/env python
#
# Copyright 2009 unscatter.com
#
# This source code is proprietary and owned by jbowman and may not
# be copied, distributed, or run without prior permission from the owner.
__author__="bowman.joseph@gmail.com"
__date__ ="$September 24, 2011 1:50:35 PM$"
session = {
"COOKIE_NAME": "mosession",
"DEFAULT_COOKIE_PATH": "/",
"SESSION_EXPIRE_TIME": 7200, # sessions are valid for 7200 seconds
# (2 hours)
"SET_COOKIE_EXPIRES": True, # Set to True to add expiration field to
# cookie
"SESSION_TOKEN_TTL": 5, # Number of seconds a session token is valid
# for.
"UPDATE_LAST_ACTIVITY": 60, # Number of seconds that may pass before
# last_activity is updated
"MONGO_COLLECTION": 'mosessions',
"MONGO_COLLECTION_SIZE": 100000,
}
@joerussbowman
Copy link
Author

Path is
mosessions/
init.py
settings.py

The example handler is one that uses the @Mosession decorator...

umm import to get the decorator is

from mosessions import mosession

What's happening when I try to run it is something isn't getting returned right. Maybe I'm missing a yield somewhere?

Traceback (most recent call last):
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/web.py", line 1144, in _when_complete
if result.result() is not None:
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/concurrent.py", line 129, in result
raise_exc_info(self.__exc_info)
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/gen.py", line 221, in wrapper
runner.run()
File "/home/joe/dev/bod/bod-ve/local/lib/python2.7/site-packages/tornado/gen.py", line 507, in run
yielded = self.gen.send(next)
File "/home/joe/dev/bod/mosessions/init.py", line 217, in wrapper
self.session = yield MoSession(self)
TypeError: init() should return None, not 'generator'

@joerussbowman
Copy link
Author

I updated it, got rid of the gen.coroutine stuff and used callbacks. Was getting a blank page so added a bunch of debugging. The first time I use motor to connect to mongodb it releases everything and runs the request handler. So the set up stuff never happens.

Request looks like
[E 131129 15:56:06 init:224] starting
[E 131129 15:56:06 init:73] starting init
[E 131129 15:56:06 init:96] got cookie b72a364b-b4f7-466f-9ceb-5e6afa727d9e@529685870b49a55ff1272ebf
[E 131129 15:56:06 init:98] looking up session
[I 131129 15:56:06 web:1635] 304 GET /dealer/created/?confirm=8bdbabc5eb7e4a6b87b3281d1e62a3f0 (76.114.245.105) 2.42ms
[E 131129 15:56:06 init:215] doing finish
[E 131129 15:56:06 init:115] validating cookie
[E 131129 15:56:06 init:106] starting new session
[E 131129 15:56:06 init:139] storing id
[E 131129 15:56:06 init:150] setting cookie and running request handler

I need a way to make sure the callback in the session object calls the request object after that's done. I'm not sure why it's skipping ahead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment