Commits

Randy Syring committed a023409

session: re-factored cookie related settings, added httponly support

httponly support is Python 2.6+ only, warning will be given for
usage on < 2.6

also re-factored cookie related settings to try and avoid duplicated
code.

refs #62 Some way to set httponly cookies

Comments (0)

Files changed (2)

beaker/session.py

         Domain to use for the cookie.
     ``secure``
         Whether or not the cookie should only be sent over SSL.
+    ``httponly``
+        Whether or not the cookie should only be accessible by the browser
+        not by JavaScript.
     """
     def __init__(self, request, id=None, invalidate_corrupt=False,
                  use_cookies=True, type=None, data_dir=None,
                  key='beaker.session.id', timeout=None, cookie_expires=True,
                  cookie_domain=None, secret=None, secure=False,
-                 namespace_class=None, **namespace_args):
+                 namespace_class=None, httponly=False, **namespace_args):
         if not type:
             if data_dir:
                 self.type = 'file'
         self.was_invalidated = False
         self.secret = secret
         self.secure = secure
+        self.httponly = httponly
         self.id = id
         self.accessed_dict = {}
 
                 else:
                     raise
 
+    def _set_cookie_values(self, expires=None):
+        self.cookie[self.key] = self.id
+        if self._domain:
+            self.cookie[self.key]['domain'] = self._domain
+        if self.secure:
+            self.cookie[self.key]['secure'] = True
+        self._set_cookie_http_only()
+        self.cookie[self.key]['path'] = self._path
+
+        self._set_cookie_expires(expires)
+
+    def _set_cookie_expires(self, expires):
+        if expires is None:
+            if self.cookie_expires is not True:
+                if self.cookie_expires is False:
+                    expires = datetime.fromtimestamp( 0x7FFFFFFF )
+                elif isinstance(self.cookie_expires, timedelta):
+                    expires = datetime.utcnow() + self.cookie_expires
+                elif isinstance(self.cookie_expires, datetime):
+                    expires = self.cookie_expires
+                else:
+                    raise ValueError("Invalid argument for cookie_expires: %s"
+                                     % repr(self.cookie_expires))
+            else:
+                expires = None
+        if expires is not None:
+            self.cookie[self.key]['expires'] = \
+                expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
+        return expires
+
+    def _update_cookie_out(self, set_cookie=True):
+        self.request['cookie_out'] = self.cookie[self.key].output(header='')
+        self.request['set_cookie'] = set_cookie
+
+    def _set_cookie_http_only(self):
+        try:
+            if self.httponly:
+                self.cookie[self.key]['httponly'] = True
+        except Cookie.CookieError, e:
+            if 'Invalid Attribute httponly' not in str(e):
+                raise
+            util.warn('Python 2.6+ is required to use httponly')
+
     def _create_id(self):
         id_str = "%f%s%f%s" % (
                     time.time(),
         self.is_new = True
         self.last_accessed = None
         if self.use_cookies:
-            self.cookie[self.key] = self.id
-            if self._domain:
-                self.cookie[self.key]['domain'] = self._domain
-            if self.secure:
-                self.cookie[self.key]['secure'] = True
-            self.cookie[self.key]['path'] = self._path
-            if self.cookie_expires is not True:
-                if self.cookie_expires is False:
-                    expires = datetime.fromtimestamp( 0x7FFFFFFF )
-                elif isinstance(self.cookie_expires, timedelta):
-                    expires = datetime.utcnow() + self.cookie_expires
-                elif isinstance(self.cookie_expires, datetime):
-                    expires = self.cookie_expires
-                else:
-                    raise ValueError("Invalid argument for cookie_expires: %s"
-                                     % repr(self.cookie_expires))
-                self.cookie[self.key]['expires'] = \
-                    expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
-            self.request['cookie_out'] = self.cookie[self.key].output(header='')
-            self.request['set_cookie'] = False
+            self._set_cookie_values()
+            self._update_cookie_out(set_cookie=False)
 
     def created(self):
         return self['_creation_time']
     def _set_domain(self, domain):
         self['_domain'] = domain
         self.cookie[self.key]['domain'] = domain
-        self.request['cookie_out'] = self.cookie[self.key].output(header='')
-        self.request['set_cookie'] = True
+        self._update_cookie_out()
 
     def _get_domain(self):
         return self._domain
     def _set_path(self, path):
         self['_path'] = path
         self.cookie[self.key]['path'] = path
-        self.request['cookie_out'] = self.cookie[self.key].output(header='')
-        self.request['set_cookie'] = True
+        self._update_cookie_out()
 
     def _get_path(self):
         return self._path
 
     def _delete_cookie(self):
         self.request['set_cookie'] = True
-        self.cookie[self.key] = self.id
-        if self._domain:
-            self.cookie[self.key]['domain'] = self._domain
-        if self.secure:
-            self.cookie[self.key]['secure'] = True
-        self.cookie[self.key]['path'] = '/'
         expires = datetime.utcnow().replace(year=2003)
-        self.cookie[self.key]['expires'] = \
-            expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
-        self.request['cookie_out'] = self.cookie[self.key].output(header='')
-        self.request['set_cookie'] = True
+        self._set_cookie_values(expires)
+        self._update_cookie_out()
 
     def delete(self):
         """Deletes the session from the persistent storage, and sends
         Domain to use for the cookie.
     ``secure``
         Whether or not the cookie should only be sent over SSL.
+    ``httponly``
+        Whether or not the cookie should only be accessible by the browser
+        not by JavaScript.
 
     """
     def __init__(self, request, key='beaker.session.id', timeout=None,
                  cookie_expires=True, cookie_domain=None, encrypt_key=None,
-                 validate_key=None, secure=False, **kwargs):
+                 validate_key=None, secure=False, httponly=False, **kwargs):
 
         if not crypto.has_aes and encrypt_key:
             raise InvalidCryptoBackendError("No AES library is installed, can't generate "
         self.validate_key = validate_key
         self.request['set_cookie'] = False
         self.secure = secure
+        self.httponly = httponly
         self._domain = cookie_domain
         self._path = '/'
 
             self['_id'] = self._make_id()
         self['_accessed_time'] = time.time()
 
-        if self.cookie_expires is not True:
-            if self.cookie_expires is False:
-                expires = datetime.fromtimestamp( 0x7FFFFFFF )
-            elif isinstance(self.cookie_expires, timedelta):
-                expires = datetime.utcnow() + self.cookie_expires
-            elif isinstance(self.cookie_expires, datetime):
-                expires = self.cookie_expires
-            else:
-                raise ValueError("Invalid argument for cookie_expires: %s"
-                                 % repr(self.cookie_expires))
-            self['_expires'] = expires
-        elif '_expires' in self:
+        if '_expires' in self:
             expires = self['_expires']
         else:
             expires = None
+        expires = self._set_cookie_expires(expires)
+        if expires is not None:
+            self['_expires'] = expires
 
         val = self._encrypt_data()
         if len(val) > 4064:
             self.cookie[self.key]['domain'] = self._domain
         if self.secure:
             self.cookie[self.key]['secure'] = True
+        self._set_cookie_http_only()
 
         self.cookie[self.key]['path'] = self.get('_path', '/')
 
-        if expires:
-            self.cookie[self.key]['expires'] = \
-                expires.strftime("%a, %d-%b-%Y %H:%M:%S GMT" )
         self.request['cookie_out'] = self.cookie[self.key].output(header='')
         self.request['set_cookie'] = True
 

tests/test_session.py

 # -*- coding: utf-8 -*-
+import sys
 import time
+import warnings
 
 from beaker.session import Session
 from beaker import util
     assert 'beaker.session.id=%s' % session.id in session.request['cookie_out']
     assert 'expires=' in session.request['cookie_out']
 
+    # test for secure
+    session = get_session(use_cookies=True, secure=True)
+    assert 'secure' in session.request['cookie_out']
+
+    # test for httponly
+    class ShowWarning(object):
+        def __init__(self):
+            self.msg = None
+        def __call__(self, message, category, filename, lineno, file=None, line=None):
+            self.msg = str(message)
+    orig_sw = warnings.showwarning
+    sw = ShowWarning()
+    warnings.showwarning = sw
+    session = get_session(use_cookies=True, httponly=True)
+    if sys.version_info < (2, 6):
+        assert sw.msg == 'Python 2.6+ is required to use httponly'
+    else:
+        assert 'httponly' in session.request['cookie_out']
+    warnings.showwarning = orig_sw
 
 def test_cookies_disabled():
     """