Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

A CGI Framework in Python (Web Techniques, Feb 1998)


#  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] )

Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips 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.