Merge branch 'master' of github.com:Pylons/pyramid
| | |
| | | Unreleased |
| | | ========== |
| | | 1.5a3 (2013-12-10) |
| | | ================== |
| | | |
| | | Features |
| | | -------- |
| | |
| | | See https://github.com/Pylons/pyramid/pull/1149 |
| | | |
| | | - Added a new ``SignedCookieSessionFactory`` which is very similar to the |
| | | ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on |
| | | signing content. The custom serializer arguments to this function should |
| | | only focus on serializing, unlike its predecessor which required the |
| | | serializer to also perform signing. |
| | | See https://github.com/Pylons/pyramid/pull/1142 |
| | | ``UnencryptedCookieSessionFactoryConfig`` but with a clearer focus on signing |
| | | content. The custom serializer arguments to this function should only focus |
| | | on serializing, unlike its predecessor which required the serializer to also |
| | | perform signing. See https://github.com/Pylons/pyramid/pull/1142 . Note |
| | | that cookies generated using ``SignedCookieSessionFactory`` are not |
| | | compatible with cookies generated using ``UnencryptedCookieSessionFactory``, |
| | | so existing user session data will be destroyed if you switch to it. |
| | | |
| | | - Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie |
| | | factory that can be used by framework implementors to create their own |
| | |
| | | does not need to be in ``k=v`` form. This is useful if you want to be able |
| | | 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 |
| | | --------- |
| | |
| | | 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) |
| | | ================== |
| | | |
| | |
| | | def aview(request): |
| | | localizer = request.localizer |
| | | num = 1 |
| | | translated = localizer.pluralize( |
| | | _('item_plural', default="${number} items"), |
| | | None, num, 'mydomain', mapping={'number':num} |
| | | ) |
| | | translated = localizer.pluralize('item_plural', '${number} items', |
| | | num, 'mydomain', mapping={'number':num}) |
| | | |
| | | The corresponding message catalog must have language plural definitions and |
| | | plural alternatives set. |
| | |
| | | signing content. The custom serializer arguments to this function should |
| | | only focus on serializing, unlike its predecessor which required the |
| | | serializer to also perform signing. |
| | | See https://github.com/Pylons/pyramid/pull/1142 |
| | | See https://github.com/Pylons/pyramid/pull/1142 . Note |
| | | that cookies generated using ``SignedCookieSessionFactory`` are not |
| | | compatible with cookies generated using ``UnencryptedCookieSessionFactory``, |
| | | so existing user session data will be destroyed if you switch to it. |
| | | |
| | | - Added a new ``BaseCookieSessionFactory`` which acts as a generic cookie |
| | | factory that can be used by framework implementors to create their own |
| | |
| | | |
| | | - Pyramid no longer depends upon ``Mako`` or ``Chameleon``. |
| | | |
| | | - Pyramid now depends on WebOb>=1.3 (it uses ``webob.cookies.CookieProfile`` |
| | | from 1.3+). |
| | |
| | | |
| | | from zope.interface import implementer |
| | | |
| | | from webob.cookies import CookieProfile |
| | | |
| | | from pyramid.compat import ( |
| | | long, |
| | | text_type, |
| | |
| | | url_quote, |
| | | bytes_, |
| | | ascii_native_, |
| | | native_, |
| | | ) |
| | | |
| | | from pyramid.interfaces import ( |
| | |
| | | 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 |
| | |
| | | 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: |
| | |
| | | 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`` |
| | |
| | | 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, |
| | |
| | | ) |
| | | |
| | | 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): |
| | |
| | | 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) |
| | | |
| | |
| | | :term:`message identifier` objects as a singular/plural pair |
| | | and an ``n`` value representing the number that appears in the |
| | | message using gettext plural forms support. The ``singular`` |
| | | and ``plural`` objects passed may be translation strings or |
| | | unicode strings. ``n`` represents the number of elements. |
| | | ``domain`` is the translation domain to use to do the |
| | | pluralization, and ``mapping`` is the interpolation mapping |
| | | that should be used on the result. Note that if the objects |
| | | passed are translation strings, their domains and mappings are |
| | | ignored. The domain and mapping arguments must be used |
| | | instead. If the ``domain`` is not supplied, a default domain |
| | | is used (usually ``messages``). |
| | | |
| | | and ``plural`` objects should be unicode strings. There is no |
| | | reason to use translation string objects as arguments as all |
| | | metadata is ignored. |
| | | |
| | | ``n`` represents the number of elements. ``domain`` is the |
| | | translation domain to use to do the pluralization, and ``mapping`` |
| | | is the interpolation mapping that should be used on the result. If |
| | | the ``domain`` is not supplied, a default domain is used (usually |
| | | ``messages``). |
| | | |
| | | Example:: |
| | | |
| | | num = 1 |
| | |
| | | num, |
| | | mapping={'num':num}) |
| | | |
| | | If using the gettext plural support, which is required for |
| | | languages that have pluralisation rules other than n != 1, the |
| | | ``singular`` argument must be the message_id defined in the |
| | | translation file. The plural argument is not used in this case. |
| | | |
| | | Example:: |
| | | |
| | | num = 1 |
| | | translated = localizer.pluralize('item_plural', |
| | | '', |
| | | num, |
| | | mapping={'num':num}) |
| | | |
| | | |
| | | """ |
| | | if self.pluralizer is None: |
| | |
| | | from zope.deprecation import deprecated |
| | | from zope.interface import implementer |
| | | |
| | | from webob.cookies import SignedSerializer |
| | | |
| | | from pyramid.compat import ( |
| | | pickle, |
| | | PY3, |
| | |
| | | return False |
| | | return True |
| | | |
| | | class PickleSerializer(object): |
| | | """ A Webob cookie serializer that uses the pickle protocol to dump Python |
| | | data to bytes.""" |
| | | def loads(self, bstruct): |
| | | return pickle.loads(bstruct) |
| | | |
| | | def dumps(self, appstruct): |
| | | return pickle.dumps(appstruct, pickle.HIGHEST_PROTOCOL) |
| | | |
| | | def BaseCookieSessionFactory( |
| | | serialize, |
| | | deserialize, |
| | | serializer, |
| | | cookie_name='session', |
| | | max_age=None, |
| | | path='/', |
| | |
| | | |
| | | Parameters: |
| | | |
| | | ``serialize`` |
| | | A callable accepting a Python object and returning a bytestring. A |
| | | ``ValueError`` should be raised for malformed inputs. |
| | | |
| | | ``deserialize`` |
| | | A callable accepting a bytestring and returning a Python object. A |
| | | ``ValueError`` should be raised for malformed inputs. |
| | | ``serializer`` |
| | | An object with two methods: `loads`` and ``dumps``. The ``loads`` method |
| | | should accept bytes and return a Python object. The ``dumps`` method |
| | | should accept a Python object and return bytes. A ``ValueError`` should |
| | | be raised for malformed inputs. |
| | | |
| | | ``cookie_name`` |
| | | The name of the cookie used for sessioning. Default: ``'session'``. |
| | |
| | | cookieval = request.cookies.get(self._cookie_name) |
| | | if cookieval is not None: |
| | | try: |
| | | value = deserialize(bytes_(cookieval)) |
| | | value = serializer.loads(bytes_(cookieval)) |
| | | except ValueError: |
| | | # the cookie failed to deserialize, dropped |
| | | value = None |
| | |
| | | exception = getattr(self.request, 'exception', None) |
| | | if exception is not None: # dont set a cookie during exceptions |
| | | return False |
| | | cookieval = native_(serialize( |
| | | cookieval = native_(serializer.dumps( |
| | | (self.accessed, self.created, dict(self)) |
| | | )) |
| | | if len(cookieval) > 4064: |
| | |
| | | """ |
| | | .. deprecated:: 1.5 |
| | | Use :func:`pyramid.session.SignedCookieSessionFactory` instead. |
| | | Caveat: Cookies generated using ``SignedCookieSessionFactory`` are not |
| | | compatible with cookies generated using |
| | | ``UnencryptedCookieSessionFactory``, so existing user session data will |
| | | be destroyed if you switch to it. |
| | | |
| | | Configure a :term:`session factory` which will provide unencrypted |
| | | (but signed) cookie-based sessions. The return value of this |
| | |
| | | is valid. Default: ``signed_deserialize`` (using pickle). |
| | | """ |
| | | |
| | | class SerializerWrapper(object): |
| | | def __init__(self, secret): |
| | | self.secret = secret |
| | | |
| | | def loads(self, bstruct): |
| | | return signed_deserialize(bstruct, secret) |
| | | |
| | | def dumps(self, appstruct): |
| | | return signed_serialize(appstruct, secret) |
| | | |
| | | serializer = SerializerWrapper(secret) |
| | | |
| | | return BaseCookieSessionFactory( |
| | | lambda v: signed_serialize(v, secret), |
| | | lambda v: signed_deserialize(v, secret), |
| | | serializer, |
| | | cookie_name=cookie_name, |
| | | max_age=cookie_max_age, |
| | | path=cookie_path, |
| | |
| | | deprecated( |
| | | 'UnencryptedCookieSessionFactoryConfig', |
| | | 'The UnencryptedCookieSessionFactoryConfig callable is deprecated as of ' |
| | | 'Pyramid 1.5. Use ``pyramid.session.SignedCookieSessionFactory`` instead.' |
| | | 'Pyramid 1.5. Use ``pyramid.session.SignedCookieSessionFactory`` instead. ' |
| | | 'Caveat: Cookies generated using SignedCookieSessionFactory are not ' |
| | | 'compatible with cookies generated using UnencryptedCookieSessionFactory, ' |
| | | 'so existing user session data will be destroyed if you switch to it.' |
| | | ) |
| | | |
| | | def SignedCookieSessionFactory( |
| | |
| | | reissue_time=0, |
| | | hashalg='sha512', |
| | | salt='pyramid.session.', |
| | | serialize=None, |
| | | deserialize=None, |
| | | serializer=None, |
| | | ): |
| | | """ |
| | | .. versionadded:: 1.5 |
| | |
| | | If ``True``, set a session cookie even if an exception occurs |
| | | while rendering a view. Default: ``True``. |
| | | |
| | | ``serialize`` |
| | | A callable accepting a Python object and returning a bytestring. A |
| | | ``ValueError`` should be raised for malformed inputs. |
| | | Default: :func:`pickle.dumps`. |
| | | |
| | | ``deserialize`` |
| | | A callable accepting a bytestring and returning a Python object. A |
| | | ``ValueError`` should be raised for malformed inputs. |
| | | Default: :func:`pickle.loads`. |
| | | ``serializer`` |
| | | An object with two methods: `loads`` and ``dumps``. The ``loads`` method |
| | | should accept bytes and return a Python object. The ``dumps`` method |
| | | should accept a Python object and return bytes. A ``ValueError`` should |
| | | be raised for malformed inputs. If a serializer is not passed, the |
| | | :class:`pyramid.session.PickleSerializer` serializer will be used. |
| | | |
| | | .. versionadded: 1.5a3 |
| | | """ |
| | | if serializer is None: |
| | | serializer = PickleSerializer() |
| | | |
| | | if serialize is None: |
| | | serialize = lambda v: pickle.dumps(v, pickle.HIGHEST_PROTOCOL) |
| | | |
| | | if deserialize is None: |
| | | deserialize = pickle.loads |
| | | |
| | | digestmod = lambda string=b'': hashlib.new(hashalg, string) |
| | | digest_size = digestmod().digest_size |
| | | |
| | | salted_secret = bytes_(salt or '') + bytes_(secret) |
| | | |
| | | def signed_serialize(appstruct): |
| | | cstruct = serialize(appstruct) |
| | | sig = hmac.new(salted_secret, cstruct, digestmod).digest() |
| | | return base64.b64encode(cstruct + sig) |
| | | |
| | | def signed_deserialize(bstruct): |
| | | try: |
| | | fstruct = base64.b64decode(bstruct) |
| | | except (binascii.Error, TypeError) as e: |
| | | raise ValueError('Badly formed base64 data: %s' % e) |
| | | |
| | | cstruct = fstruct[:-digest_size] |
| | | expected_sig = fstruct[-digest_size:] |
| | | |
| | | sig = hmac.new(salted_secret, cstruct, digestmod).digest() |
| | | if strings_differ(sig, expected_sig): |
| | | raise ValueError('Invalid signature') |
| | | |
| | | return deserialize(cstruct) |
| | | signed_serializer = SignedSerializer( |
| | | secret, |
| | | salt, |
| | | hashalg, |
| | | serializer=serializer, |
| | | ) |
| | | |
| | | return BaseCookieSessionFactory( |
| | | signed_serialize, |
| | | signed_deserialize, |
| | | signed_serializer, |
| | | cookie_name=cookie_name, |
| | | max_age=max_age, |
| | | path=path, |
| | |
| | | method = 'GET' |
| | | application_url = 'http://example.com' |
| | | host = 'example.com:80' |
| | | domain = 'example.com' |
| | | content_length = 0 |
| | | query_string = '' |
| | | charset = 'UTF-8' |
| | |
| | | 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 ] |
| | |
| | | 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') |
| | |
| | | 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): |
| | |
| | | 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): |
| | |
| | | |
| | | 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): |
| | |
| | | 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): |
| | |
| | | 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 |
| | |
| | | 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): |
| | |
| | | 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): |
| | |
| | | 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') |
| | |
| | | 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') |
| | |
| | | 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): |
| | |
| | | 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 |
| | | |
| | |
| | | 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 {} |
| | |
| | | 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 |
| | | |
| | |
| | | class TestBaseCookieSession(SharedCookieSessionTests, unittest.TestCase): |
| | | def _makeOne(self, request, **kw): |
| | | from pyramid.session import BaseCookieSessionFactory |
| | | return BaseCookieSessionFactory( |
| | | dummy_serialize, dummy_deserialize, **kw)(request) |
| | | serializer = DummySerializer() |
| | | return BaseCookieSessionFactory(serializer, **kw)(request) |
| | | |
| | | def _serialize(self, value): |
| | | return json.dumps(value) |
| | |
| | | digestmod = lambda: hashlib.new(hashalg) |
| | | cstruct = pickle.dumps(value, pickle.HIGHEST_PROTOCOL) |
| | | sig = hmac.new(salt + b'secret', cstruct, digestmod).digest() |
| | | return base64.b64encode(cstruct + sig) |
| | | return base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=') |
| | | |
| | | def test_reissue_not_triggered(self): |
| | | import time |
| | |
| | | import hmac |
| | | import time |
| | | request = testing.DummyRequest() |
| | | cstruct = dummy_serialize((time.time(), 0, {'state': 1})) |
| | | serializer = DummySerializer() |
| | | cstruct = serializer.dumps((time.time(), 0, {'state': 1})) |
| | | sig = hmac.new(b'pyramid.session.secret', cstruct, sha512).digest() |
| | | cookieval = base64.b64encode(cstruct + sig) |
| | | cookieval = base64.urlsafe_b64encode(sig + cstruct).rstrip(b'=') |
| | | request.cookies['session'] = cookieval |
| | | session = self._makeOne(request, deserialize=dummy_deserialize) |
| | | session = self._makeOne(request, serializer=serializer) |
| | | self.assertEqual(session['state'], 1) |
| | | |
| | | def test_invalid_data_size(self): |
| | |
| | | |
| | | try: |
| | | result = callbacks[0](request, response) |
| | | except TypeError as e: # pragma: no cover |
| | | except TypeError: # pragma: no cover |
| | | self.fail('HMAC failed to initialize due to key length.') |
| | | |
| | | self.assertEqual(result, None) |
| | |
| | | kw.setdefault(dest, kw.pop(src)) |
| | | |
| | | def _serialize(self, value): |
| | | from pyramid.compat import bytes_ |
| | | from pyramid.session import signed_serialize |
| | | return signed_serialize(value, 'secret') |
| | | return bytes_(signed_serialize(value, 'secret')) |
| | | |
| | | def test_serialize_option(self): |
| | | from pyramid.response import Response |
| | |
| | | result = self._callFUT(request, 'csrf_token', raises=False) |
| | | self.assertEqual(result, False) |
| | | |
| | | def dummy_serialize(value): |
| | | return json.dumps(value).encode('utf-8') |
| | | class DummySerializer(object): |
| | | def dumps(self, value): |
| | | return json.dumps(value).encode('utf-8') |
| | | |
| | | def dummy_deserialize(value): |
| | | return json.loads(value.decode('utf-8')) |
| | | def loads(self, value): |
| | | return json.loads(value.decode('utf-8')) |
| | | |
| | | class DummySessionFactory(dict): |
| | | _dirty = False |
| | |
| | | .. warning:: if no ``elements`` arguments are specified, the resource |
| | | URL will end with a trailing slash. If any |
| | | ``elements`` are used, the generated URL will *not* |
| | | end in trailing a slash. |
| | | end in a trailing slash. |
| | | |
| | | If a keyword argument ``query`` is present, it will be used to compose |
| | | a query string that will be tacked on to the end of the URL. The value |
| | |
| | | |
| | | 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 |
| | |
| | | ] |
| | | |
| | | setup(name='pyramid', |
| | | version='1.5a2', |
| | | version='1.5a3', |
| | | description='The Pyramid Web Framework, a Pylons project', |
| | | long_description=README + '\n\n' + CHANGES, |
| | | classifiers=[ |