Modified _auth_tkt in line with paste upstream
Refreshed _auth_tkt to bring it in line with upstream paste auth_tkt.py
This brings with it support for non-MD5 digests.
All changes to _auth_tkt.py came from upstream.
It is not however a synchronised file, repoze variations have been
retained.
Changes were made to test__auth_tkt.py to allow tests to pass.
The number of arguments for calculate_digest() has changed requiring the
tests to be updated. This is an internal function and should not impact
users.
| | |
| | | makes it possible to use the same authentication process with |
| | | non-Python code run under Apache. |
| | | """ |
| | | from hashlib import md5 |
| | | import time as time_mod |
| | | try: |
| | | import hashlib |
| | | except ImportError: |
| | | # mimic hashlib (will work for md5, fail for secure hashes) |
| | | import md5 as hashlib |
| | | |
| | | from repoze.who._compat import encodestring |
| | | from repoze.who._compat import SimpleCookie |
| | | from repoze.who._compat import url_quote |
| | | from repoze.who._compat import url_unquote |
| | | |
| | | DEFAULT_DIGEST = hashlib.md5 |
| | | |
| | | |
| | | class AuthTicket(object): |
| | |
| | | the shared secret, the userid, and the IP address. Optionally you |
| | | can include tokens (a list of strings, representing role names), |
| | | 'user_data', which is arbitrary data available for your own use in |
| | | later scripts. Lastly, you can override the cookie name and |
| | | timestamp. |
| | | later scripts. Lastly, you can override the timestamp, cookie name, |
| | | whether to secure the cookie and the digest algorithm (for details |
| | | look at ``AuthTKTMiddleware``). |
| | | |
| | | Once you provide all the arguments, use .cookie_value() to |
| | | generate the appropriate authentication ticket. .cookie() |
| | |
| | | |
| | | token = auth_tkt.AuthTick('sharedsecret', 'username', |
| | | os.environ['REMOTE_ADDR'], tokens=['admin']) |
| | | print 'Status: 200 OK' |
| | | print 'Content-type: text/html' |
| | | print token.cookie() |
| | | print |
| | | print('Status: 200 OK') |
| | | print('Content-type: text/html') |
| | | print(token.cookie()) |
| | | print("") |
| | | ... redirect HTML ... |
| | | |
| | | Webware usage:: |
| | |
| | | |
| | | def __init__(self, secret, userid, ip, tokens=(), user_data='', |
| | | time=None, cookie_name='auth_tkt', |
| | | secure=False): |
| | | secure=False, digest_algo=DEFAULT_DIGEST): |
| | | self.secret = secret |
| | | self.userid = userid |
| | | self.ip = ip |
| | |
| | | self.time = time |
| | | self.cookie_name = cookie_name |
| | | self.secure = secure |
| | | if isinstance(digest_algo, str): |
| | | # correct specification of digest from hashlib or fail |
| | | self.digest_algo = getattr(hashlib, digest_algo) |
| | | else: |
| | | self.digest_algo = digest_algo |
| | | |
| | | def digest(self): |
| | | return calculate_digest( |
| | | self.ip, self.time, self.secret, self.userid, self.tokens, |
| | | self.user_data) |
| | | self.user_data, self.digest_algo) |
| | | |
| | | def cookie_value(self): |
| | | v = '%s%08x%s!' % (self.digest(), int(self.time), |
| | |
| | | Exception.__init__(self, msg) |
| | | |
| | | |
| | | def parse_ticket(secret, ticket, ip): |
| | | def parse_ticket(secret, ticket, ip, digest_algo=DEFAULT_DIGEST): |
| | | """ |
| | | Parse the ticket, returning (timestamp, userid, tokens, user_data). |
| | | |
| | | If the ticket cannot be parsed, ``BadTicket`` will be raised with |
| | | an explanation. |
| | | """ |
| | | if isinstance(digest_algo, str): |
| | | # correct specification of digest from hashlib or fail |
| | | digest_algo = getattr(hashlib, digest_algo) |
| | | digest_hexa_size = digest_algo().digest_size * 2 |
| | | ticket = ticket.strip('"') |
| | | digest = ticket[:32] |
| | | digest = ticket[:digest_hexa_size] |
| | | try: |
| | | timestamp = int(ticket[32:40], 16) |
| | | timestamp = int(ticket[digest_hexa_size:digest_hexa_size + 8], 16) |
| | | except ValueError as e: |
| | | raise BadTicket('Timestamp is not a hex integer: %s' % e) |
| | | try: |
| | | userid, data = ticket[40:].split('!', 1) |
| | | userid, data = ticket[digest_hexa_size + 8:].split('!', 1) |
| | | except ValueError: |
| | | raise BadTicket('userid is not followed by !') |
| | | userid = url_unquote(userid) |
| | |
| | | user_data = data |
| | | |
| | | expected = calculate_digest(ip, timestamp, secret, |
| | | userid, tokens, user_data) |
| | | userid, tokens, user_data, |
| | | digest_algo) |
| | | |
| | | if expected != digest: |
| | | raise BadTicket('Digest signature is not correct', |
| | |
| | | return (timestamp, userid, tokens, user_data) |
| | | |
| | | |
| | | def calculate_digest(ip, timestamp, secret, userid, tokens, user_data): |
| | | def calculate_digest(ip, timestamp, secret, userid, tokens, user_data, |
| | | digest_algo): |
| | | secret = maybe_encode(secret) |
| | | userid = maybe_encode(userid) |
| | | tokens = maybe_encode(tokens) |
| | | user_data = maybe_encode(user_data) |
| | | digest0 = md5( |
| | | digest0 = digest_algo( |
| | | encode_ip_timestamp(ip, timestamp) + secret + userid + b'\0' |
| | | + tokens + b'\0' + user_data).hexdigest() |
| | | digest = md5(maybe_encode(digest0) + secret).hexdigest() |
| | | digest = digest_algo(maybe_encode(digest0) + secret).hexdigest() |
| | | return digest |
| | | |
| | | |
| | |
| | | self.assertEqual(tkt.secure, True) |
| | | |
| | | def test_digest(self): |
| | | from .._auth_tkt import calculate_digest |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | tkt = self._makeOne('SEEKRIT', 'USERID', '1.2.3.4', tokens=('a', 'b'), |
| | | user_data='DATA', time=_WHEN, |
| | | cookie_name='oatmeal', secure=True) |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', |
| | | 'a,b', 'DATA') |
| | | 'a,b', 'DATA', hashlib.md5) |
| | | self.assertEqual(tkt.digest(), digest) |
| | | |
| | | def test_cookie_value_wo_tokens_or_userdata(self): |
| | | from .._auth_tkt import calculate_digest |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | tkt = self._makeOne('SEEKRIT', 'USERID', '1.2.3.4', time=_WHEN) |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', '', '') |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', '', '', hashlib.md5) |
| | | self.assertEqual(tkt.cookie_value(), |
| | | '%s%08xUSERID!' % (digest, _WHEN)) |
| | | |
| | | def test_cookie_value_w_tokens_and_userdata(self): |
| | | from .._auth_tkt import calculate_digest |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | tkt = self._makeOne('SEEKRIT', 'USERID', '1.2.3.4', tokens=('a', 'b'), |
| | | user_data='DATA', time=_WHEN) |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', |
| | | 'a,b', 'DATA') |
| | | 'a,b', 'DATA', hashlib.md5) |
| | | self.assertEqual(tkt.cookie_value(), |
| | | '%s%08xUSERID!a,b!DATA' % (digest, _WHEN)) |
| | | |
| | | def test_cookie_not_secure_wo_tokens_or_userdata(self): |
| | | from .._auth_tkt import calculate_digest |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | from .._compat import encodestring |
| | | tkt = self._makeOne('SEEKRIT', 'USERID', '1.2.3.4', time=_WHEN, |
| | | cookie_name='oatmeal') |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', '', '') |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', '', '', hashlib.md5) |
| | | cookie = tkt.cookie() |
| | | self.assertEqual(cookie['oatmeal'].value, |
| | | encodestring('%s%08xUSERID!' % (digest, _WHEN) |
| | |
| | | self.assertEqual(cookie['oatmeal']['secure'], '') |
| | | |
| | | def test_cookie_secure_w_tokens_and_userdata(self): |
| | | from .._auth_tkt import calculate_digest |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | from .._compat import encodestring |
| | | tkt = self._makeOne('SEEKRIT', 'USERID', '1.2.3.4', tokens=('a', 'b'), |
| | | user_data='DATA', time=_WHEN, |
| | | cookie_name='oatmeal', secure=True) |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', |
| | | 'a,b', 'DATA') |
| | | 'a,b', 'DATA', hashlib.md5) |
| | | cookie = tkt.cookie() |
| | | self.assertEqual(cookie['oatmeal'].value, |
| | | encodestring('%s%08xUSERID!a,b!DATA' % (digest, _WHEN) |
| | |
| | | self.fail('Did not raise') |
| | | |
| | | def test_wo_tokens_or_data_ok_digest(self): |
| | | from .._auth_tkt import calculate_digest |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', '', '') |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', '', '', hashlib.md5) |
| | | TICKET = '%s%08xUSERID!' % (digest, _WHEN) |
| | | timestamp, userid, tokens, user_data = self._callFUT(ticket=TICKET) |
| | | self.assertEqual(timestamp, _WHEN) |
| | |
| | | self.assertEqual(user_data, '') |
| | | |
| | | def test_w_tokens_and_data_ok_digest(self): |
| | | from .._auth_tkt import calculate_digest |
| | | from .._auth_tkt import calculate_digest, hashlib |
| | | digest = calculate_digest('1.2.3.4', _WHEN, 'SEEKRIT', 'USERID', |
| | | 'a,b', 'DATA') |
| | | 'a,b', 'DATA', hashlib.md5) |
| | | TICKET = '%s%08xUSERID!a,b!DATA' % (digest, _WHEN) |
| | | timestamp, userid, tokens, user_data = self._callFUT(ticket=TICKET) |
| | | self.assertEqual(timestamp, _WHEN) |