Created
March 13, 2012 15:27
-
-
Save ewr/2029404 to your computer and use it in GitHub Desktop.
Rails-compatible Django Cookie Sessions. More explanation at http://ericrichardson.com/2012/03/1786-making-django-and-rails-play-nice-part-2
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Be sure to restart your server when you modify this file. | |
class JSONVerifier < ActiveSupport::MessageVerifier | |
def verify(signed_message) | |
raise InvalidSignature if signed_message.blank? | |
data, digest = signed_message.split("--") | |
if data.present? && digest.present? && secure_compare(digest, generate_digest(data)) | |
ActiveSupport::JSON.decode(Base64.decode64(data.gsub('%3D','='))) | |
else | |
raise InvalidSignature | |
end | |
end | |
def generate(value) | |
# If it isn't present, add in session_expiry to support django | |
if value.is_a?(Hash) && !value.has_key?("_session_expiry") | |
# expire in 30 days | |
value['_session_expiry'] = (Time.now() + 30*86400).strftime("%s") | |
end | |
data = Base64.strict_encode64(ActiveSupport::JSON.encode(value)) | |
"#{data}--#{generate_digest(data)}" | |
end | |
end | |
module ActionDispatch | |
class Cookies | |
class SignedCookieJar | |
def initialize(parent_jar, secret) | |
ensure_secret_secure(secret) | |
@parent_jar = parent_jar | |
@verifier = JSONVerifier.new(secret) | |
end | |
end | |
end | |
end | |
MyApp::Application.config.session_store :cookie_store, key: '_myapp_session' |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from django.conf import settings | |
import hashlib | |
import hmac | |
import base64 | |
import simplejson | |
import datetime | |
import re | |
from OpenSSL import rand | |
from django.contrib.sessions.backends.base import SessionBase | |
class SessionStore(SessionBase): | |
def load(self): | |
""" | |
We load the data from the key itself instead of fetching from | |
some external data store. Opposite of _get_session_key(), | |
raises BadSignature if signature fails. | |
""" | |
dd = self._session_key.split("--") | |
# make sure we got both elements | |
if len(dd) == 2: | |
data = re.sub('%3D','=',dd[0]) | |
# now make sure digest is valid | |
if dd[1] == self.generate_digest(data): | |
# valid. decode and load data | |
obj = simplejson.loads(base64.b64decode(data)) | |
# intercept _session_expiry | |
if obj.has_key("_session_expiry"): | |
obj['_session_expiry'] = datetime.datetime.fromtimestamp(int(obj['_session_expiry'])) | |
return obj | |
# if we get here, it was invalid and we should generate a new session | |
self.create() | |
return {} | |
def create(self): | |
""" | |
To create a new key, we simply make sure that the modified flag is set | |
so that the cookie is set on the client for the current request. | |
""" | |
self.modified = True | |
def save(self, must_create=False): | |
""" | |
To save, we get the session key as a securely signed string and then | |
set the modified flag so that the cookie is set on the client for the | |
current request. | |
""" | |
self._session_key = self._get_session_key() | |
self.modified = True | |
def exists(self, session_key=None): | |
""" | |
This method makes sense when you're talking to a shared resource, but | |
it doesn't matter when you're storing the information in the client's | |
cookie. | |
""" | |
return False | |
def delete(self, session_key=None): | |
""" | |
To delete, we clear the session key and the underlying data structure | |
and set the modified flag so that the cookie is set on the client for | |
the current request. | |
""" | |
self._session_key = '' | |
self._session_cache = {} | |
self.modified = True | |
def cycle_key(self): | |
""" | |
Keeps the same data but with a new key. To do this, we just have to | |
call ``save()`` and it will automatically save a cookie with a new key | |
at the end of the request. | |
""" | |
self.save() | |
def get_expiry_age(self): | |
""" | |
Ensure we always get a datetime expiry | |
""" | |
expiry = self.get('_session_expiry') | |
if not expiry: # Checks both None and 0 cases | |
return settings.SESSION_COOKIE_AGE | |
if not isinstance(expiry, datetime.datetime): | |
try: | |
expiry = datetime.datetime.fromtimestamp(int(expiry)) | |
except: | |
return expiry | |
delta = expiry - datetime.datetime.now() | |
return delta.days * 86400 + delta.seconds | |
def _get_session_key(self): | |
""" | |
Most session backends don't need to override this method, but we do, | |
because instead of generating a random string, we want to actually | |
generate a secure url-safe Base64-encoded string of data as our | |
session key. | |
""" | |
obj = getattr(self, '_session_cache', {}) | |
# intercept _session_expiry | |
if obj.has_key("_session_expiry") and isinstance(obj['_session_expiry'],datetime.datetime): | |
obj['_session_expiry'] = obj['_session_expiry'].strftime("%s") | |
# add session_id if it's not present | |
if not obj.has_key("session_id"): | |
obj['session_id'] = rand.bytes(16).encode('hex_codec') | |
# Dump to JSON, then encode as base64 | |
enc = base64.b64encode(simplejson.dumps(obj)) | |
return "--".join([re.sub('=','%3D',enc),self.generate_digest(enc)]) | |
def generate_digest(self,data): | |
return hmac.new(settings.SECRET_KEY,data,hashlib.sha1).hexdigest() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment