BasicAuth.py :  » Web-Server » SkunkWEB » skunkweb-3.4.4 » pylibs » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Web Server » SkunkWEB 
SkunkWEB » skunkweb 3.4.4 » pylibs » BasicAuth.py
#  
#  Copyright (C) 2001 Andrew T. Csillag <drew_csillag@geocities.com>
#  
#      You may distribute under the terms of either the GNU General
#      Public License or the SkunkWeb License, as specified in the
#      README file.
#   
"""
This module implements the back-end details of "basic" 
authentication as used by many HTTP servers 
(and, actually, Unix logins).

Basic authentication consists of accepting a submitted 
username and password and comparing it to a username/password
pair stored by some device. On the surface, pretty basic,
right?

This module provides classes which can be the building blocks
for your own authentication objects, which could use
a variety of sources to store the username/password pairs.
See the DictAuthenticator and Cryptor classes for details.

This module also implements a fully functional, usable class
called CryptedFileAuthenticator, which will perform
authentication against Apache "htpasswd" files and even
Unix passwd files...

The biggest caveat: To use the CryptedFileAuthenticator, you 
must have your Python interpreter built with
the crypt module. It's not installed by default,
so you will have to hack around in the Python source
and re-install, or verify that your binary installation
does include the crypt module. If you don't want to use
crypt, you can use the other contents of this module
without a hitch.
"""

import os, types, string

# hacky wrap of crypt module import,
# so that someone is told what to do about 
# the absence of crypt...
try:
    import crypt
except ImportError:
    crypt = None

from types import StringType

class Authenticator:
    """
    Defines the interface for a basic authenticator.
    All "public" methods must be implemented by subclasses.

    All "private" methods (those starting with _) are
    optional to implement; their default behavior is as simple
    as possible, often "pass".
    """
    # required public methods
    def add_user(self, username, password, flush=1): 
  raise NotImplementedError, "Authenticator.add_user method"
    def remove_user(self, username, flush=1): 
  raise NotImplementedError, "Authenticator.remove_user method"
    def is_user(self, username): 
  raise NotImplementedError, "Authenticator.is_user method"
    def is_valid(self, username, password): 
  raise NotImplementedError, "Authenticator.is_valid method"

    # optional private methods
    def _encrypt_password(self, password):
  """
  If overridden, should return the "encrypted"
  version of password. By default, this method
  returns the password intact ("unecrypted").
  """
  return password

    def _match_password(self, password, candidate):
  """
  Returns 1 or 0, whether the real password
  password and the submitted password 
  candidate (which is assumed to be 
  unencrypted) are equal.  By default, this method 
  just returns (password == candidate).
  """
  return password == candidate

class DictAuthenticator(Authenticator):
    users = {}

    def __init__(self, users={}):
  if type(users) != types.DictType:
      raise TypeError, "users must be a dictionary: %s" % users
        self.users()
  self.reset()

    def add_user(self, username, password, flush=1):
  """
  Adds (or updates) a user entry in its user database.
  username and password must be non-empty strings, or a 
  TypeError is raised. flush, if true, causes the 
  flush() method to be called after adding the user entry.
  """
  if type(username) != StringType or not username:
      raise TypeError, "user %s must be a string" % repr(user)
  if type(password) != StringType or not password:
      raise TypeError, "password %s must be a non-empty string" % repr(password)
  password = self._encrypt_password(password)
        self.users[username] = password
  if flush: self.flush()

    def remove_user(self, username, flush=1):
  """
  Removes the user identified by username from the database.
  If flush is true, the database is written out to the auth file
  afer removing the user. If the user does not exist in the database,
  this method silently returns.
  """
  # sliently return if user not present
  if not self.users.has_key(username): return
  del self.users[username]
  if flush: self.flush()

    def is_user(self, username):
  """
  Tells whether the user identified by username
  exists in the user database. Returns 1 or 0.
  """
  return self.users.has_key(username)

    def is_valid(self, username, password):
  """
  Tells whether the username and password
  identify a valid entry in the user database.
  Returns 1 or 0. password cannot be empty and must be in plaintext:
  don't try to crypt.crypt() it yourself!
  """
  if not type(password) == StringType or not password: return 0
  p = self.users.get(username)
  if not p: return 0
  return self._match_password(p, password)

    # public persistence methods
    def reset(self): 
  """
  This method, if overridden, should "reset" the users
  dictionary attribute so that it has no "dirty" or "stale"
  information in memory. Overrider reset only if the users
  attribute is a memory cache for username/password pairs.
  If you are not caching in memory, but merely accessing
  your persistence directly, you may leave this method
  as is. By default, reset does nothing.
  """
  pass

    def flush(self): 
  """
  This method, if overridden, should flush all "dirty"
  or "unwritten" username/password pairs to persistence,
  such as a file or database. If you are accessing your
  persistence directly, and not caching "dirty" or "new"
  entries in self.users, leave this method as is. By default,
  flush does nothing.
  """
  pass

class FileAuthenticator(DictAuthenticator):
    auth_file = None

    def __init__(self, auth_file):
  """
  auth_file is the path to the auth file.
  """
        self.auth_file = auth_file
  self.reset()

    def flush(self):
  """
  Writes out the user database to the auth file.
  """

  fd = self._open_for_write()
  for u, p in self.users.items():
      fd.write("%s:%s\n" % (u,p))
  fd.close()

    def reset(self):
  """
  Causes the user database to be reloaded from disk.
  Called by __init__, and you shouldn't need to call
  it unless you're doing strange things.
  """
  # clear out users
  self.users = {}
  fd = self._open_for_read()
  lines = fd.readlines()
  fd.close()
  for line in lines:
      self._parse_line(line)

    # special private methods for this class
    def _open_for_read(self):
  try:
      return open(self.auth_file, 'r')
        except:
      raise IOError, "cannot open auth file %s for read" % self.auth_file

    def _open_for_write(self):
  try:
      return open(self.auth_file, 'w')
        except:
      raise IOError, "cannot open auth file %s for write" % self.auth_file

    def _parse_line(self, line):
  if not line: return
  # strip the newline
  line = string.replace(line, '\n', '')
  # split on colon
  spls = string.split(line, ':')
  # if != 2 items, skip
  if len(spls) < 2: return
  # first is username, second is crypted password
  # password must be at least 3 chars crypted!
  if len(spls[1]) <= 2: return
  # assign to user hash
  self.users[spls[0]] = spls[1]

# stand-in for exception raised if crypt module not available
EncryptionError = 'EncryptionError'

class Cryptor:
    """
    A mix-in class which implements encryption
    using the Unix crypt(3) call. Only works if
    the crypt module has been built into your Python
    install. If the crypt module is not available,
    an "EncryptionError" will be raised whenever
    a crypt operation would be attempted.

    This class works by overriding two methods
    which are present in any class which implements
    the Authenticator interface.

    When mixing in this class, be sure to have it
    precede other base classes in this module, such
    as FileAuthenticator! Otherwise your crypted
    methods will not work properly.
    """

    def _encrypt_password(self, password):
  if crypt is None:
      raise EncryptionError, \
      "encrypted authentication not possible without crypt module"
  if len(password) < 2: salt = 'XX'
  else: salt = password[0:2]
        return crypt.crypt(password, salt)

    def _match_password(self, password, candidate):
  if crypt is None:
      raise EncryptionError, \
      "encrypted authentication not possible without crypt module"
  salt = password[0:2]
  return crypt.crypt(candidate, salt) == password

# note that Cryptor must come before Fileauthenticator in bases
class CryptedFileAuthenticator(Cryptor, FileAuthenticator):
    """
    Combines FileAuthenticator and Cryptor to provide
    authentication against an Apache htpasswd file, or
    a Unix passwd file, or any file whose lines start of
    the form:

    <username>:<crypted_password>[:<other_garbage>][\n]
    """

# a caching "registry" for crypted passwd files,

_crypted_auth_registry = {}

# and a caching factory function for crypted authenticators

def get_crypted_authenticator(filename, auth_type='htpasswd'):
    """
    A nice factory function. Hand it a file path,
    and it will return a CryptedFileAuthenticator instance.
    It also stores the CryptedFileAuthenticator in a registry
    under its filename, so that subsequent calls to
    this function with the same filename return immediately.

    If filename is not a non-empty string, a TypeError is raised.
    """
    if type(filename) != StringType or not filename: 
  raise TypeError, "filename %s must be a non-empty string" % repr(filename)
    if _crypted_auth_registry.has_key(filename): return _crypted_auth_registry[filename]
    if not os.path.isfile(filename):
  raise IOError, "Basic auth file %s does not exist!" % filename
    a = CryptedFileAuthenticator(filename)
    _crypted_auth_registry[filename] = a
    return a

if __name__ == '__main__':
    file = '/usr/local/skunk/etc/apache.auth'
    a = get_crypted_authenticator(file)
    u, p = '__total__test__fooboy', 'mrman'
    for un, pw in a.users.items():
  if not a.is_user(un): raise RuntimeError
  if un == u:
      raise RuntimeError, \
      "Oh my god, the test user %s is in the db!" % u
        print (un, pw)
    a.add_user(u, p)
    print "user added"
    print a.is_user(u) and "added user tested"
    print a.is_valid(u, p) and "added user re-tested"
    print not a.is_valid(u, p + 'blah') and "added user re-re-tested"
    a.remove_user(u, p)
    print "user removed"
    print a.is_user(u) or "user removal tested"
    print a.is_valid(u, p) or "user removal re-tested"

www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.