summaryrefslogtreecommitdiff
path: root/paste
diff options
context:
space:
mode:
authorIan Bicking <ianb@colorstudy.com>2012-08-17 16:05:35 -0500
committerIan Bicking <ianb@colorstudy.com>2012-08-17 16:05:35 -0500
commit7b0218607dc97af6d03f5397dbf17b5910fae82e (patch)
tree5fa33fa144917a583e6a0daf0149c0274839938f /paste
parent9fae57022f1164ea72196dd8e91f73a0463427d8 (diff)
parent663ec52ddd568148e000f1e7d2ea65d2ec284dd5 (diff)
downloadpaste-7b0218607dc97af6d03f5397dbf17b5910fae82e.tar.gz
Merged in mitchellrj/paste/double_slash_at_start_of_path_fix (pull request #10)
Diffstat (limited to 'paste')
-rw-r--r--paste/auth/auth_tkt.py59
-rw-r--r--paste/auth/digest.py33
2 files changed, 72 insertions, 20 deletions
diff --git a/paste/auth/auth_tkt.py b/paste/auth/auth_tkt.py
index 8dfce00..f3240ee 100644
--- a/paste/auth/auth_tkt.py
+++ b/paste/auth/auth_tkt.py
@@ -39,14 +39,17 @@ non-Python code run under Apache.
import time as time_mod
try:
- from hashlib import md5
+ import hashlib
except ImportError:
- from md5 import md5
+ # mimic hashlib (will work for md5, fail for secure hashes)
+ import md5 as hashlib
import Cookie
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 +58,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()
@@ -86,7 +90,7 @@ 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
@@ -98,11 +102,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))
@@ -132,21 +141,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)
+ timestamp = int(ticket[digest_hexa_size:digest_hexa_size + 8], 16)
except ValueError, 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 +171,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 +183,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 +250,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 +275,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 +286,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 +309,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'):
diff --git a/paste/auth/digest.py b/paste/auth/digest.py
index 483c38d..9b427aa 100644
--- a/paste/auth/digest.py
+++ b/paste/auth/digest.py
@@ -38,6 +38,34 @@ except ImportError:
import time, random
from urllib import quote as url_quote
+def _split_auth_string(auth_string):
+ """ split a digest auth string into individual key=value strings """
+ prev = None
+ for item in auth_string.split(","):
+ try:
+ if prev.count('"') == 1:
+ prev = "%s,%s" % (prev, item)
+ continue
+ except AttributeError:
+ if prev == None:
+ prev = item
+ continue
+ else:
+ raise StopIteration
+ yield prev.strip()
+ prev = item
+
+ yield prev.strip()
+ raise StopIteration
+
+def _auth_to_kv_pairs(auth_string):
+ """ split a digest auth string into key, value pairs """
+ for item in _split_auth_string(auth_string):
+ (k, v) = item.split("=", 1)
+ if v.startswith('"') and len(v) > 1 and v.endswith('"'):
+ v = v[1:-1]
+ yield (k, v)
+
def digest_password(realm, username, password):
""" construct the appropriate hashcode needed for HTTP digest """
return md5("%s:%s:%s" % (username, realm, password)).hexdigest()
@@ -98,10 +126,7 @@ class AuthDigestAuthenticator(object):
(authmeth, auth) = authorization.split(" ", 1)
if 'digest' != authmeth.lower():
return self.build_authentication()
- amap = {}
- for itm in auth.split(","):
- (k,v) = [s.strip() for s in itm.strip().split("=", 1)]
- amap[k] = v.replace('"', '')
+ amap = dict(_auth_to_kv_pairs(auth))
try:
username = amap['username']
authpath = amap['uri']