1.1 --- a/WebStack/Helpers/Auth.py Mon Nov 12 00:49:00 2007 +0000
1.2 +++ b/WebStack/Helpers/Auth.py Mon Nov 12 00:50:03 2007 +0000
1.3 @@ -22,6 +22,10 @@
1.4
1.5 import base64
1.6 import md5
1.7 +try:
1.8 + import hmac
1.9 +except ImportError:
1.10 + hmac = None
1.11
1.12 class UserInfo:
1.13
1.14 @@ -50,6 +54,136 @@
1.15
1.16 self.username, self.password = None, None
1.17
1.18 +# Classes providing support for authentication resources.
1.19 +
1.20 +class Authenticator:
1.21 +
1.22 + """
1.23 + A simple authenticator with no other purpose than to return the status of an
1.24 + authentication request.
1.25 + """
1.26 +
1.27 + def __init__(self, credentials):
1.28 +
1.29 + """
1.30 + Initialise the authenticator with a registry of 'credentials'.
1.31 +
1.32 + The 'credentials' must be an object which supports tests of the form
1.33 + '(username, password) in credentials'.
1.34 + """
1.35 +
1.36 + self.credentials = credentials
1.37 +
1.38 + def authenticate(self, trans, username, password):
1.39 +
1.40 + """
1.41 + Authenticate the sender of the transaction 'trans', returning a true
1.42 + value if they are recognised, or a false value otherwise. Use the
1.43 + 'username' and 'password' supplied as credentials.
1.44 + """
1.45 +
1.46 + # Check against the class's credentials.
1.47 +
1.48 + return (username, password) in self.credentials
1.49 +
1.50 +class Verifier:
1.51 +
1.52 + """
1.53 + An authenticator which can only verify an authentication attempt with
1.54 + existing credentials, not check new credentials.
1.55 + """
1.56 +
1.57 + def __init__(self, secret_key, cookie_name=None):
1.58 +
1.59 + """
1.60 + Initialise the authenticator with a 'secret_key' and an optional
1.61 + 'cookie_name'.
1.62 + """
1.63 +
1.64 + self.secret_key = secret_key
1.65 + self.cookie_name = cookie_name or "LoginAuthenticator"
1.66 +
1.67 + def _encode(self, username):
1.68 + return username.replace(":", "%3A")
1.69 +
1.70 + def _decode(self, encoded_username):
1.71 + return encoded_username.replace("%3A", ":")
1.72 +
1.73 + def authenticate(self, trans):
1.74 +
1.75 + """
1.76 + Authenticate the originator of 'trans', returning a true value if
1.77 + successful, or a false value otherwise.
1.78 + """
1.79 +
1.80 + # Test the token from the cookie against a recreated token using the
1.81 + # given information.
1.82 +
1.83 + details = self.get_username_and_token(trans)
1.84 + if details is None:
1.85 + return 0
1.86 + else:
1.87 + username, token = details
1.88 + return token == get_token(self._encode(username), self.secret_key)
1.89 +
1.90 + def get_username_and_token(self, trans):
1.91 +
1.92 + "Return the username and token for the user."
1.93 +
1.94 + cookie = trans.get_cookie(self.cookie_name)
1.95 + if cookie is None or cookie.value is None:
1.96 + return None
1.97 + else:
1.98 + return self._decode(cookie.value.split(":")[0]), cookie.value
1.99 +
1.100 + def set_token(self, trans, username):
1.101 +
1.102 + "Set an authentication token in 'trans' with the given 'username'."
1.103 +
1.104 + trans.set_cookie_value(
1.105 + self.cookie_name,
1.106 + get_token(self._encode(username), self.secret_key),
1.107 + path="/"
1.108 + )
1.109 +
1.110 + def unset_token(self, trans):
1.111 +
1.112 + "Unset the authentication token in 'trans'."
1.113 +
1.114 + trans.delete_cookie(self.cookie_name)
1.115 +
1.116 +class LoginAuthenticator(Authenticator, Verifier):
1.117 +
1.118 + """
1.119 + An authenticator which sets authentication tokens.
1.120 + """
1.121 +
1.122 + def __init__(self, secret_key, credentials, cookie_name=None):
1.123 +
1.124 + """
1.125 + Initialise the authenticator with a 'secret_key', the authenticator's registry of
1.126 + 'credentials' and an optional 'cookie_name'.
1.127 +
1.128 + The 'credentials' must be an object which supports tests of the form
1.129 + '(username, password) in credentials'.
1.130 + """
1.131 +
1.132 + Authenticator.__init__(self, credentials)
1.133 + Verifier.__init__(self, secret_key, cookie_name)
1.134 +
1.135 + def authenticate(self, trans, username, password):
1.136 +
1.137 + """
1.138 + Authenticate the sender of the transaction 'trans', returning a true
1.139 + value if they are recognised, or a false value otherwise. Use the
1.140 + 'username' and 'password' supplied as credentials.
1.141 + """
1.142 +
1.143 + valid = Authenticator.authenticate(self, trans, username, password)
1.144 + if valid:
1.145 + self.set_token(trans, username)
1.146 + return valid
1.147 +
1.148 def get_token(plaintext, secret_key):
1.149
1.150 """
1.151 @@ -59,4 +193,40 @@
1.152
1.153 return plaintext + ":" + md5.md5(plaintext + secret_key).hexdigest()
1.154
1.155 +if hmac is not None:
1.156 +
1.157 + def get_openid_token(items, secret_key):
1.158 +
1.159 + """
1.160 + Return a string containing the 'items' encoded using the given
1.161 + 'secret_key'.
1.162 + """
1.163 +
1.164 + plaintext = "\n".join([(key + ":" + value) for (key, value) in items]) + "\n"
1.165 + hash = hmac.new(secret_key, plaintext)
1.166 + return base64.standard_b64encode(hash.digest())
1.167 +
1.168 + def check_openid_signature(fields, secret_key):
1.169 +
1.170 + """
1.171 + Return whether information in the given 'fields' (a mapping from names
1.172 + to lists of values) is signed using the 'secret_key'.
1.173 + """
1.174 +
1.175 + signed_names = fields["openid.signed"][0].split(",")
1.176 + return fields["openid.sig"][0] == make_openid_signature(signed_names, fields, secret_key)
1.177 +
1.178 + def make_openid_signature(signed_names, fields, secret_key):
1.179 +
1.180 + """
1.181 + Make and return a signature using the 'signed_names' to indicate which
1.182 + values from the 'fields' (a mapping from names to lists of values) shall
1.183 + be signed using the 'secret_key'.
1.184 + """
1.185 +
1.186 + items = []
1.187 + for name in signed_names:
1.188 + items.append((name, fields["openid." + name][0]))
1.189 + return get_openid_token(items, secret_key)
1.190 +
1.191 # vim: tabstop=4 expandtab shiftwidth=4