# User registration module Error = "User.Error exception" # The following function will open a GDBM database and return the # database object; a module-level private variable is used so that # the file will only be opened once. _gdbmDB = None def _openDB(): global _gdbmDB if _gdbmDB == None: import gdbm _gdbmDB = gdbm.open('userDatabase', 'c', 0600) return _gdbmDB class User: "The fundamental class that represents a user" # The constructor for a User object; if more fields were # added, they should be initialized here. def __init__(self, userID="", password=""): self.userID, self.password = userID, password def getCookie(self): "Produce the string required to set a cookie containing a login token" token = makeToken(self.userID) return 'userID=%s; path=/' % (token,) def save(self): "Save the object's current state into the database" import pickle db = _openDB() db[ self.userID ] = pickle.dumps(self) # This is some super-secret information, hashed together # with the userID to generate a (hopefully) unforgeable # token. It's some random bytes from /dev/urandom on my Linux box. _secretInfo = "\016\327\132\154\215\256\373\023\362\132\047\023" def makeToken(userID): "Given a user ID, compute the hash and return the resulting token" import md5, base64, string digest = md5.new(_secretInfo + userID + _secretInfo).digest() # The token will contain the user ID and the digest, separated by / token = userID + '/' + digest # To avoid illegal characters in the digest, the token is base-64 encoded. token = base64.encodestring(token) # base64.encodestring adds a newline to its result, so we have to # strip it off return string.strip(token) def checkToken(token): """Given a user ID and a token, verify that the digest portion matches the user ID. Returns false if it fails, and the user ID if it succeeds.""" import md5, base64, string # Undo the base-64 encoding token = base64.decodestring(token+'\n') slash = string.find(token, '/') if slash == -1: return "" # Malformed authentication token # Check that the digest matches. userID = token[:slash] digest = md5.new(_secretInfo + userID + _secretInfo).digest() if digest!=token[slash+1:]: return "" # Digest portion of token doesn't match # The token checks out. return userID def createUser(userID, password, headers): """Create a new user with the given ID and password. Raises User.Error if the name's already taken, or if the password fails some simple checks. A cookie is handed out containing the authentication token.""" import regex db = _openDB() if db.has_key(userID): raise Error, "There's already a user by that name." elif password == "": raise Error, "You can't leave the password empty." elif password == userID: raise Error, "It's a bad idea to use your ID as your password." elif regex.match('^[A-Za-z_0-9]+$', userID) == -1: raise Error, "User IDs can only contain letters, numbers, and underscores." # It looks like it's OK to create the user, so create a new User # object and call its save() method, telling the object to store itself # in the database. userObj = User(userID, password) userObj.save() token = makeToken(userID) headers['Set-Cookie'] = userObj.getCookie() return userObj def loginUser(userID, password, headers): """Log in a user; this requires checking the password, and handing out a cookie containing the authentication token.""" db = _openDB() import pickle if not db.has_key(userID): raise Error, "There's either no such user, or the password is incorrect." userObj = pickle.loads( db[userID] ) if password != userObj.password: raise Error, "There's either no such user, or the password is incorrect." # It's OK, so drop a cookie on the user headers['Set-Cookie'] = userObj.getCookie() return userObj def getCurrentUser(environ): """Read the cookie to determine the user's ID, and returns their User object. Returns None if no ID could be determined.""" try: cookie = environ['HTTP_COOKIE'] except KeyError: return None # No cookie present # There's a complete cookie module available for Python, but we'll # just use a regex to get the cookie's value. The following # regular expression looks kind of ugly because the regex module # uses an Emacs-like regex syntax. In Python 1.5, the new re # module will provide regular expressions that support almost all # the features of Perl's regexes. import regex IDpat = regex.compile('userID[ \t]*=[ \t]*\([^ \t;]*\)') if IDpat.search(cookie) == -1: # No match for the regex could be found return None # Get the token, and verify that it's correct. If it is, token = IDpat.group(1) userID = checkToken(token) if userID: db = _openDB() import pickle if not db.has_key(userID): return None return pickle.loads( db[userID] )
A CGI Framework in Python (Web Techniques, Feb 1998)
Related Reading
More Insights
INFO-LINK
To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy. | |