import itertools from zope.interface import implements from repoze.who.interfaces import IAuthenticator from repoze.who.utils import resolveDotted class HTPasswdPlugin(object): implements(IAuthenticator) def __init__(self, filename, check): self.filename = filename self.check = check # IAuthenticatorPlugin def authenticate(self, environ, identity): try: login = identity['login'] password = identity['password'] except KeyError: return None if hasattr(self.filename, 'seek'): # assumed to have a readline self.filename.seek(0) f = self.filename else: try: f = open(self.filename, 'r') except IOError: environ['repoze.who.logger'].warn('could not open htpasswd ' 'file %s' % self.filename) return None result = None for line in f: try: username, hashed = line.rstrip().split(':', 1) except ValueError: continue if username == login: if self.check(password, hashed): result = username # Don't bail early: leaks information!! return result def __repr__(self): return '<%s %s>' % (self.__class__.__name__, id(self)) #pragma NO COVERAGE PADDING = ' ' * 1000 def _same_string(x, y): match = True for a, b, ignored in itertools.izip_longest(x, y, PADDING): match = a == b and match return match def crypt_check(password, hashed): from crypt import crypt salt = hashed[:2] return _same_string(hashed, crypt(password, salt)) def plain_check(password, hashed): return _same_string(password, hashed) def make_plugin(filename=None, check_fn=None): if filename is None: raise ValueError('filename must be specified') if check_fn is None: raise ValueError('check_fn must be specified') check = resolveDotted(check_fn) return HTPasswdPlugin(filename, check)