# -*- coding: utf-8 -*-
"""
webapp2_extras.securecookie
===========================
A serializer for signed cookies.
:copyright: 2011 by tipfy.org.
:license: Apache Sotware License, see LICENSE for details.
"""
import Cookie
import hashlib
import hmac
import logging
import time
from webapp2_extras import json
from webapp2_extras import security
class SecureCookieSerializer(object):
"""Serializes and deserializes secure cookie values.
Extracted from `Tornado`_ and modified.
"""
def __init__(self, secret_key):
"""Initiliazes the serializer/deserializer.
:param secret_key:
A random string to be used as the HMAC secret for the cookie
signature.
"""
self.secret_key = secret_key
def serialize(self, name, value):
"""Serializes a signed cookie value.
:param name:
Cookie name.
:param value:
Cookie value to be serialized.
:returns:
A serialized value ready to be stored in a cookie.
"""
timestamp = str(self._get_timestamp())
value = self._encode(value)
signature = self._get_signature(name, value, timestamp)
return '|'.join([value, timestamp, signature])
def deserialize(self, name, value, max_age=None):
"""Deserializes a signed cookie value.
:param name:
Cookie name.
:param value:
A cookie value to be deserialized.
:param max_age:
Maximum age in seconds for a valid cookie. If the cookie is older
than this, returns None.
:returns:
The deserialized secure cookie, or None if it is not valid.
"""
if not value:
return None
# Unquote for old WebOb.
value = Cookie._unquote(value)
parts = value.split('|')
if len(parts) != 3:
return None
signature = self._get_signature(name, parts[0], parts[1])
if not security.compare_hashes(parts[2], signature):
logging.warning('Invalid cookie signature %r', value)
return None
if max_age is not None:
if int(parts[1]) < self._get_timestamp() - max_age:
logging.warning('Expired cookie %r', value)
return None
try:
return self._decode(parts[0])
except Exception, e:
logging.warning('Cookie value failed to be decoded: %r', parts[0])
return None
def _encode(self, value):
return json.b64encode(value)
def _decode(self, value):
return json.b64decode(value)
def _get_timestamp(self):
return int(time.time())
def _get_signature(self, *parts):
"""Generates an HMAC signature."""
signature = hmac.new(self.secret_key, digestmod=hashlib.sha1)
signature.update('|'.join(parts))
return signature.hexdigest()