diff options
Diffstat (limited to 'paste/auth/auth_tkt.py')
-rw-r--r-- | paste/auth/auth_tkt.py | 81 |
1 files changed, 57 insertions, 24 deletions
diff --git a/paste/auth/auth_tkt.py b/paste/auth/auth_tkt.py index 8dfce00..da8ddbd 100644 --- a/paste/auth/auth_tkt.py +++ b/paste/auth/auth_tkt.py @@ -39,14 +39,21 @@ non-Python code run under Apache. import time as time_mod try: - from hashlib import md5 + import hashlib except ImportError: - from md5 import md5 -import Cookie + # mimic hashlib (will work for md5, fail for secure hashes) + import md5 as hashlib +try: + from http.cookies import SimpleCookie +except ImportError: + # Python 2 + from Cookie import SimpleCookie from paste import request from urllib import quote as url_quote from urllib import unquote as url_unquote +DEFAULT_DIGEST = hashlib.md5 + class AuthTicket(object): @@ -55,8 +62,9 @@ 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() @@ -67,10 +75,10 @@ class AuthTicket(object): 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:: @@ -86,11 +94,13 @@ class AuthTicket(object): 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.tokens = ','.join(tokens) + if not isinstance(tokens, basestring): + tokens = ','.join(tokens) + self.tokens = tokens self.user_data = user_data if time is None: self.time = time_mod.time() @@ -98,11 +108,16 @@ class AuthTicket(object): 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), url_quote(self.userid)) @@ -112,7 +127,7 @@ class AuthTicket(object): return v def cookie(self): - c = Cookie.SimpleCookie() + c = SimpleCookie() c[self.cookie_name] = self.cookie_value().encode('base64').strip().replace('\n', '') c[self.cookie_name]['path'] = '/' if self.secure: @@ -132,21 +147,25 @@ class BadTicket(Exception): 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) - except ValueError, e: + 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) @@ -158,7 +177,8 @@ def parse_ticket(secret, ticket, ip): 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', @@ -169,15 +189,17 @@ def parse_ticket(secret, ticket, ip): return (timestamp, userid, tokens, user_data) -def calculate_digest(ip, timestamp, secret, userid, tokens, user_data): +# @@: Digest object constructor compatible with named ones in hashlib only +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 + '\0' + tokens + '\0' + user_data).hexdigest() - digest = md5(digest0 + secret).hexdigest() + digest = digest_algo(digest0 + secret).hexdigest() return digest @@ -234,6 +256,12 @@ class AuthTKTMiddleware(object): page will be shown as usual, but the user will also be logged out when they visit this page. + ``digest_algo``: + Digest algorithm specified as a name of the algorithm provided by + ``hashlib`` or as a compatible digest object constructor. + Defaults to ``md5``, as in mod_auth_tkt. The others currently + compatible with mod_auth_tkt are ``sha256`` and ``sha512``. + If used with mod_auth_tkt, then these settings (except logout_path) should match the analogous Apache configuration settings. @@ -253,7 +281,7 @@ class AuthTKTMiddleware(object): def __init__(self, app, secret, cookie_name='auth_tkt', secure=False, include_ip=True, logout_path=None, httponly=False, no_domain_cookie=True, current_domain_cookie=True, - wildcard_cookie=True): + wildcard_cookie=True, digest_algo=DEFAULT_DIGEST): self.app = app self.secret = secret self.cookie_name = cookie_name @@ -264,6 +292,11 @@ class AuthTKTMiddleware(object): self.no_domain_cookie = no_domain_cookie self.current_domain_cookie = current_domain_cookie self.wildcard_cookie = wildcard_cookie + 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 __call__(self, environ, start_response): cookies = request.get_cookies(environ) @@ -282,7 +315,7 @@ class AuthTKTMiddleware(object): # Also, timeouts should cause cookie refresh try: timestamp, userid, tokens, user_data = parse_ticket( - self.secret, cookie_value, remote_addr) + self.secret, cookie_value, remote_addr, self.digest_algo) tokens = ','.join(tokens) environ['REMOTE_USER'] = userid if environ.get('REMOTE_USER_TOKENS'): |