summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt9
-rw-r--r--docs/whatsnew-1.5.rst2
-rw-r--r--pyramid/authentication.py86
-rw-r--r--pyramid/testing.py1
-rw-r--r--pyramid/tests/test_authentication.py129
-rw-r--r--setup.py2
6 files changed, 126 insertions, 103 deletions
diff --git a/CHANGES.txt b/CHANGES.txt
index 98784f3d7..0508abc61 100644
--- a/CHANGES.txt
+++ b/CHANGES.txt
@@ -66,6 +66,9 @@ Features
to use a different query string format than ``x-www-form-urlencoded``. See
https://github.com/Pylons/pyramid/pull/1183
+- ``pyramid.testing.DummyRequest`` now has a ``domain`` attribute to match the
+ new WebOb 1.3 API. Its value is ``example.com``.
+
Bug Fixes
---------
@@ -149,6 +152,12 @@ Deprecations
Instead, use the newly-added ``unauthenticated_userid`` attribute of the
request object.
+Dependencies
+------------
+
+- Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile``
+ from 1.3+).
+
1.5a2 (2013-09-22)
==================
diff --git a/docs/whatsnew-1.5.rst b/docs/whatsnew-1.5.rst
index 23613896a..bd1bdd66c 100644
--- a/docs/whatsnew-1.5.rst
+++ b/docs/whatsnew-1.5.rst
@@ -504,3 +504,5 @@ Dependency Changes
- Pyramid no longer depends upon ``Mako`` or ``Chameleon``.
+- Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile``
+ from 1.3+).
diff --git a/pyramid/authentication.py b/pyramid/authentication.py
index 2c301bd29..ba7b864f9 100644
--- a/pyramid/authentication.py
+++ b/pyramid/authentication.py
@@ -10,6 +10,8 @@ import warnings
from zope.interface import implementer
+from webob.cookies import CookieProfile
+
from pyramid.compat import (
long,
text_type,
@@ -18,6 +20,7 @@ from pyramid.compat import (
url_quote,
bytes_,
ascii_native_,
+ native_,
)
from pyramid.interfaces import (
@@ -798,8 +801,6 @@ def encode_ip_timestamp(ip, timestamp):
ts_chars = ''.join(map(chr, ts))
return bytes_(ip_chars + ts_chars)
-EXPIRE = object()
-
class AuthTktCookieHelper(object):
"""
A helper class for use in third-party authentication policy
@@ -830,55 +831,32 @@ class AuthTktCookieHelper(object):
include_ip=False, timeout=None, reissue_time=None,
max_age=None, http_only=False, path="/", wild_domain=True,
hashalg='md5', parent_domain=False, domain=None):
+
+ serializer = _SimpleSerializer()
+
+ self.cookie_profile = CookieProfile(
+ cookie_name = cookie_name,
+ secure = secure,
+ max_age = max_age,
+ httponly = http_only,
+ path = path,
+ serializer=serializer
+ )
+
self.secret = secret
self.cookie_name = cookie_name
- self.include_ip = include_ip
self.secure = secure
+ self.include_ip = include_ip
self.timeout = timeout
self.reissue_time = reissue_time
self.max_age = max_age
- self.http_only = http_only
- self.path = path
self.wild_domain = wild_domain
self.parent_domain = parent_domain
self.domain = domain
self.hashalg = hashalg
- static_flags = []
- if self.secure:
- static_flags.append('; Secure')
- if self.http_only:
- static_flags.append('; HttpOnly')
- self.static_flags = "".join(static_flags)
-
- def _get_cookies(self, environ, value, max_age=None):
- if max_age is EXPIRE:
- max_age = "; Max-Age=0; Expires=Wed, 31-Dec-97 23:59:59 GMT"
- elif max_age is not None:
- later = datetime.datetime.utcnow() + datetime.timedelta(
- seconds=int(max_age))
- # Wdy, DD-Mon-YY HH:MM:SS GMT
- expires = later.strftime('%a, %d %b %Y %H:%M:%S GMT')
- # the Expires header is *required* at least for IE7 (IE7 does
- # not respect Max-Age)
- max_age = "; Max-Age=%s; Expires=%s" % (max_age, expires)
- else:
- max_age = ''
-
- cur_domain = environ.get('HTTP_HOST', environ.get('SERVER_NAME'))
-
- # While Chrome, IE, and Firefox can cope, Opera (at least) cannot
- # cope with a port number in the cookie domain when the URL it
- # receives the cookie from does not also have that port number in it
- # (e.g via a proxy). In the meantime, HTTP_HOST is sent with port
- # number, and neither Firefox nor Chrome do anything with the
- # information when it's provided in a cookie domain except strip it
- # out. So we strip out any port number from the cookie domain
- # aggressively to avoid problems. See also
- # https://github.com/Pylons/pyramid/issues/131
- if ':' in cur_domain:
- cur_domain = cur_domain.split(':', 1)[0]
-
+ def _get_cookies(self, request, value, max_age=None):
+ cur_domain = request.domain
domains = []
if self.domain:
@@ -892,14 +870,15 @@ class AuthTktCookieHelper(object):
if self.wild_domain:
domains.append('.' + cur_domain)
- cookies = []
- base_cookie = '%s="%s"; Path=%s%s%s' % (self.cookie_name, value,
- self.path, max_age, self.static_flags)
- for domain in domains:
- domain = '; Domain=%s' % domain if domain is not None else ''
- cookies.append(('Set-Cookie', '%s%s' % (base_cookie, domain)))
+ profile = self.cookie_profile(request)
- return cookies
+ kw = {}
+ kw['domains'] = domains
+ if max_age is not None:
+ kw['max_age'] = max_age
+
+ headers = profile.get_headers(value, **kw)
+ return headers
def identify(self, request):
""" Return a dictionary with authentication information, or ``None``
@@ -968,9 +947,8 @@ class AuthTktCookieHelper(object):
def forget(self, request):
""" Return a set of expires Set-Cookie headers, which will destroy
any existing auth_tkt cookie when attached to a response"""
- environ = request.environ
request._authtkt_reissue_revoked = True
- return self._get_cookies(environ, '', max_age=EXPIRE)
+ return self._get_cookies(request, None)
def remember(self, request, userid, max_age=None, tokens=()):
""" Return a set of Set-Cookie headers; when set into a response,
@@ -1037,7 +1015,7 @@ class AuthTktCookieHelper(object):
)
cookie_value = ticket.cookie_value()
- return self._get_cookies(environ, cookie_value, max_age)
+ return self._get_cookies(request, cookie_value, max_age)
@implementer(IAuthenticationPolicy)
class SessionAuthenticationPolicy(CallbackAuthenticationPolicy):
@@ -1196,3 +1174,11 @@ class BasicAuthAuthenticationPolicy(CallbackAuthenticationPolicy):
except ValueError: # not enough values to unpack
return None
return username, password
+
+class _SimpleSerializer(object):
+ def loads(self, bstruct):
+ return native_(bstruct)
+
+ def dumps(self, appstruct):
+ return bytes_(appstruct)
+
diff --git a/pyramid/testing.py b/pyramid/testing.py
index b3460d8aa..91dc41dd5 100644
--- a/pyramid/testing.py
+++ b/pyramid/testing.py
@@ -320,6 +320,7 @@ class DummyRequest(
method = 'GET'
application_url = 'http://example.com'
host = 'example.com:80'
+ domain = 'example.com'
content_length = 0
query_string = ''
charset = 'UTF-8'
diff --git a/pyramid/tests/test_authentication.py b/pyramid/tests/test_authentication.py
index 3ac8f2d61..79d2a5923 100644
--- a/pyramid/tests/test_authentication.py
+++ b/pyramid/tests/test_authentication.py
@@ -572,7 +572,12 @@ class TestAuthTktCookieHelper(unittest.TestCase):
return DummyRequest(environ, cookie=cookie)
def _cookieValue(self, cookie):
- return eval(cookie.value)
+ items = cookie.value.split('/')
+ D = {}
+ for item in items:
+ k, v = item.split('=', 1)
+ D[k] = v
+ return D
def _parseHeaders(self, headers):
return [ self._parseHeader(header) for header in headers ]
@@ -838,7 +843,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
request.callbacks[0](None, response)
self.assertEqual(len(response.headerlist), 3)
self.assertEqual(response.headerlist[0][0], 'Set-Cookie')
- self.assertTrue("'tokens': ()" in response.headerlist[0][1])
+ self.assertTrue("/tokens=/" in response.headerlist[0][1])
def test_remember(self):
helper = self._makeOne('secret')
@@ -851,11 +856,11 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertTrue(result[0][1].startswith('auth_tkt='))
self.assertEqual(result[1][0], 'Set-Cookie')
- self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost'))
+ self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/'))
self.assertTrue(result[1][1].startswith('auth_tkt='))
self.assertEqual(result[2][0], 'Set-Cookie')
- self.assertTrue(result[2][1].endswith('; Path=/; Domain=.localhost'))
+ self.assertTrue(result[2][1].endswith('; Domain=.localhost; Path=/'))
self.assertTrue(result[2][1].startswith('auth_tkt='))
def test_remember_include_ip(self):
@@ -869,11 +874,11 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertTrue(result[0][1].startswith('auth_tkt='))
self.assertEqual(result[1][0], 'Set-Cookie')
- self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost'))
+ self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/'))
self.assertTrue(result[1][1].startswith('auth_tkt='))
self.assertEqual(result[2][0], 'Set-Cookie')
- self.assertTrue(result[2][1].endswith('; Path=/; Domain=.localhost'))
+ self.assertTrue(result[2][1].endswith('; Domain=.localhost; Path=/'))
self.assertTrue(result[2][1].startswith('auth_tkt='))
def test_remember_path(self):
@@ -889,12 +894,12 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(result[1][0], 'Set-Cookie')
self.assertTrue(result[1][1].endswith(
- '; Path=/cgi-bin/app.cgi/; Domain=localhost'))
+ '; Domain=localhost; Path=/cgi-bin/app.cgi/'))
self.assertTrue(result[1][1].startswith('auth_tkt='))
self.assertEqual(result[2][0], 'Set-Cookie')
self.assertTrue(result[2][1].endswith(
- '; Path=/cgi-bin/app.cgi/; Domain=.localhost'))
+ '; Domain=.localhost; Path=/cgi-bin/app.cgi/'))
self.assertTrue(result[2][1].startswith('auth_tkt='))
def test_remember_http_only(self):
@@ -922,15 +927,15 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(len(result), 3)
self.assertEqual(result[0][0], 'Set-Cookie')
- self.assertTrue('; Secure' in result[0][1])
+ self.assertTrue('; secure' in result[0][1])
self.assertTrue(result[0][1].startswith('auth_tkt='))
self.assertEqual(result[1][0], 'Set-Cookie')
- self.assertTrue('; Secure' in result[1][1])
+ self.assertTrue('; secure' in result[1][1])
self.assertTrue(result[1][1].startswith('auth_tkt='))
self.assertEqual(result[2][0], 'Set-Cookie')
- self.assertTrue('; Secure' in result[2][1])
+ self.assertTrue('; secure' in result[2][1])
self.assertTrue(result[2][1].startswith('auth_tkt='))
def test_remember_wild_domain_disabled(self):
@@ -944,62 +949,49 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertTrue(result[0][1].startswith('auth_tkt='))
self.assertEqual(result[1][0], 'Set-Cookie')
- self.assertTrue(result[1][1].endswith('; Path=/; Domain=localhost'))
+ self.assertTrue(result[1][1].endswith('; Domain=localhost; Path=/'))
self.assertTrue(result[1][1].startswith('auth_tkt='))
def test_remember_parent_domain(self):
helper = self._makeOne('secret', parent_domain=True)
request = self._makeRequest()
- request.environ['HTTP_HOST'] = 'www.example.com'
+ request.domain = 'www.example.com'
result = helper.remember(request, 'other')
self.assertEqual(len(result), 1)
self.assertEqual(result[0][0], 'Set-Cookie')
- self.assertTrue(result[0][1].endswith('; Path=/; Domain=.example.com'))
+ self.assertTrue(result[0][1].endswith('; Domain=.example.com; Path=/'))
self.assertTrue(result[0][1].startswith('auth_tkt='))
def test_remember_parent_domain_supercedes_wild_domain(self):
helper = self._makeOne('secret', parent_domain=True, wild_domain=True)
request = self._makeRequest()
- request.environ['HTTP_HOST'] = 'www.example.com'
+ request.domain = 'www.example.com'
result = helper.remember(request, 'other')
self.assertEqual(len(result), 1)
- self.assertTrue(result[0][1].endswith('; Domain=.example.com'))
+ self.assertTrue(result[0][1].endswith('; Domain=.example.com; Path=/'))
def test_remember_explicit_domain(self):
helper = self._makeOne('secret', domain='pyramid.bazinga')
request = self._makeRequest()
- request.environ['HTTP_HOST'] = 'www.example.com'
+ request.domain = 'www.example.com'
result = helper.remember(request, 'other')
self.assertEqual(len(result), 1)
self.assertEqual(result[0][0], 'Set-Cookie')
- self.assertTrue(result[0][1].endswith('; Path=/; Domain=pyramid.bazinga'))
+ self.assertTrue(result[0][1].endswith(
+ '; Domain=pyramid.bazinga; Path=/'))
self.assertTrue(result[0][1].startswith('auth_tkt='))
def test_remember_domain_supercedes_parent_and_wild_domain(self):
helper = self._makeOne('secret', domain='pyramid.bazinga',
parent_domain=True, wild_domain=True)
request = self._makeRequest()
- request.environ['HTTP_HOST'] = 'www.example.com'
+ request.domain = 'www.example.com'
result = helper.remember(request, 'other')
self.assertEqual(len(result), 1)
- self.assertTrue(result[0][1].endswith('; Path=/; Domain=pyramid.bazinga'))
-
- def test_remember_domain_has_port(self):
- helper = self._makeOne('secret', wild_domain=False)
- request = self._makeRequest()
- request.environ['HTTP_HOST'] = 'example.com:80'
- result = helper.remember(request, 'other')
- self.assertEqual(len(result), 2)
-
- self.assertEqual(result[0][0], 'Set-Cookie')
- self.assertTrue(result[0][1].endswith('; Path=/'))
- self.assertTrue(result[0][1].startswith('auth_tkt='))
-
- self.assertEqual(result[1][0], 'Set-Cookie')
- self.assertTrue(result[1][1].endswith('; Path=/; Domain=example.com'))
- self.assertTrue(result[1][1].startswith('auth_tkt='))
+ self.assertTrue(result[0][1].endswith(
+ '; Domain=pyramid.bazinga; Path=/'))
def test_remember_binary_userid(self):
import base64
@@ -1010,7 +1002,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(len(result), 3)
val = self._cookieValue(values[0])
self.assertEqual(val['userid'],
- bytes_(base64.b64encode(b'userid').strip()))
+ text_(base64.b64encode(b'userid').strip()))
self.assertEqual(val['user_data'], 'userid_type:b64str')
def test_remember_int_userid(self):
@@ -1044,7 +1036,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(len(result), 3)
val = self._cookieValue(values[0])
self.assertEqual(val['userid'],
- base64.b64encode(userid.encode('utf-8')))
+ text_(base64.b64encode(userid.encode('utf-8'))))
self.assertEqual(val['user_data'], 'userid_type:b64unicode')
def test_remember_insane_userid(self):
@@ -1074,13 +1066,13 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(len(result), 3)
self.assertEqual(result[0][0], 'Set-Cookie')
- self.assertTrue("'tokens': ('foo', 'bar')" in result[0][1])
+ self.assertTrue("/tokens=foo|bar/" in result[0][1])
self.assertEqual(result[1][0], 'Set-Cookie')
- self.assertTrue("'tokens': ('foo', 'bar')" in result[1][1])
+ self.assertTrue("/tokens=foo|bar/" in result[1][1])
self.assertEqual(result[2][0], 'Set-Cookie')
- self.assertTrue("'tokens': ('foo', 'bar')" in result[2][1])
+ self.assertTrue("/tokens=foo|bar/" in result[2][1])
def test_remember_unicode_but_ascii_token(self):
helper = self._makeOne('secret')
@@ -1088,7 +1080,7 @@ class TestAuthTktCookieHelper(unittest.TestCase):
la = text_(b'foo', 'utf-8')
result = helper.remember(request, 'other', tokens=(la,))
# tokens must be str type on both Python 2 and 3
- self.assertTrue("'tokens': ('foo',)" in result[0][1])
+ self.assertTrue("/tokens=foo/" in result[0][1])
def test_remember_nonascii_token(self):
helper = self._makeOne('secret')
@@ -1112,18 +1104,25 @@ class TestAuthTktCookieHelper(unittest.TestCase):
self.assertEqual(len(headers), 3)
name, value = headers[0]
self.assertEqual(name, 'Set-Cookie')
- self.assertEqual(value,
- 'auth_tkt=""; Path=/; Max-Age=0; Expires=Wed, 31-Dec-97 23:59:59 GMT')
+ self.assertEqual(
+ value,
+ 'auth_tkt=; Max-Age=0; Path=/; '
+ 'expires=Wed, 31-Dec-97 23:59:59 GMT'
+ )
name, value = headers[1]
self.assertEqual(name, 'Set-Cookie')
- self.assertEqual(value,
- 'auth_tkt=""; Path=/; Max-Age=0; '
- 'Expires=Wed, 31-Dec-97 23:59:59 GMT; Domain=localhost')
+ self.assertEqual(
+ value,
+ 'auth_tkt=; Domain=localhost; Max-Age=0; Path=/; '
+ 'expires=Wed, 31-Dec-97 23:59:59 GMT'
+ )
name, value = headers[2]
self.assertEqual(name, 'Set-Cookie')
- self.assertEqual(value,
- 'auth_tkt=""; Path=/; Max-Age=0; '
- 'Expires=Wed, 31-Dec-97 23:59:59 GMT; Domain=.localhost')
+ self.assertEqual(
+ value,
+ 'auth_tkt=; Domain=.localhost; Max-Age=0; Path=/; '
+ 'expires=Wed, 31-Dec-97 23:59:59 GMT'
+ )
class TestAuthTicket(unittest.TestCase):
def _makeOne(self, *arg, **kw):
@@ -1417,7 +1416,19 @@ class TestBasicAuthAuthenticationPolicy(unittest.TestCase):
self.assertEqual(policy.forget(None), [
('WWW-Authenticate', 'Basic realm="SomeRealm"')])
+class TestSimpleSerializer(unittest.TestCase):
+ def _makeOne(self):
+ from pyramid.authentication import _SimpleSerializer
+ return _SimpleSerializer()
+
+ def test_loads(self):
+ inst = self._makeOne()
+ self.assertEqual(inst.loads(b'abc'), text_('abc'))
+ def test_dumps(self):
+ inst = self._makeOne()
+ self.assertEqual(inst.dumps('abc'), bytes_('abc'))
+
class DummyContext:
pass
@@ -1429,6 +1440,7 @@ class DummyCookies(object):
return self.cookie
class DummyRequest:
+ domain = 'localhost'
def __init__(self, environ=None, session=None, registry=None, cookie=None):
self.environ = environ or {}
self.session = session or {}
@@ -1486,10 +1498,23 @@ class DummyAuthTktModule(object):
self.kw = kw
def cookie_value(self):
- result = {'secret':self.secret, 'userid':self.userid,
- 'remote_addr':self.remote_addr}
+ result = {
+ 'secret':self.secret,
+ 'userid':self.userid,
+ 'remote_addr':self.remote_addr
+ }
result.update(self.kw)
- result = repr(result)
+ tokens = result.pop('tokens', None)
+ if tokens is not None:
+ tokens = '|'.join(tokens)
+ result['tokens'] = tokens
+ items = sorted(result.items())
+ new_items = []
+ for k, v in items:
+ if isinstance(v, bytes):
+ v = text_(v)
+ new_items.append((k,v))
+ result = '/'.join(['%s=%s' % (k, v) for k,v in new_items ])
return result
self.AuthTicket = AuthTicket
diff --git a/setup.py b/setup.py
index 2d49717b7..d27240664 100644
--- a/setup.py
+++ b/setup.py
@@ -39,7 +39,7 @@ except IOError:
install_requires=[
'setuptools',
- 'WebOb >= 1.2b3', # request.path_info is unicode
+ 'WebOb >= 1.3', # request.domain and CookieProfile
'repoze.lru >= 0.4', # py3 compat
'zope.interface >= 3.8.0', # has zope.interface.registry
'zope.deprecation >= 3.5.0', # py3 compat