# -*- coding: utf8 -*- # # Copyright (C) 2007 Matthew Good # # "THE BEER-WARE LICENSE" (Revision 42): # wrote this file. As long as you retain this notice you # can do whatever you want with this stuff. If we meet some day, and you think # this stuff is worth it, you can buy me a beer in return. Matthew Good # # Author: Matthew Good from binascii import hexlify import md5, sha from trac.core import * from trac.config import Option from md5crypt import md5crypt class IPasswordHashMethod(Interface): def generate_hash(user, password): pass def test_hash(user, password, hash): pass class HtPasswdHashMethod(Component): implements(IPasswordHashMethod) def generate_hash(self, user, password): password = password.encode('utf-8') return htpasswd(password) def check_hash(self, user, password, hash): password = password.encode('utf-8') return hash == htpasswd(password, hash) class HtDigestHashMethod(Component): implements(IPasswordHashMethod) realm = Option('account-manager', 'htdigest_realm') def generate_hash(self, user, password): user,password,realm = _encode(user, password, self.realm) return ':'.join([realm, htdigest(user, realm, password)]) def check_hash(self, user, password, hash): user,password,realm = _encode(user, password, self.realm) return hash == self.generate_hash(user, password) def _encode(*args): return [a.encode('utf-8') for a in args] # check for the availability of the "crypt" module for checking passwords on # Unix-like platforms # MD5 is still used when adding/updating passwords try: from crypt import crypt except ImportError: crypt = None # os.urandom was added in Python 2.4 # try to fall back on reading from /dev/urandom on older Python versions try: from os import urandom except ImportError: from random import randrange def urandom(n): return ''.join([chr(randrange(256)) for _ in xrange(n)]) def salt(): s = '' v = long(hexlify(urandom(4)), 16) itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' for i in range(8): s += itoa64[v & 0x3f]; v >>= 6 return s def htpasswd(password, salt_=None): # TODO need unit test of generating new hash if salt_ is None: salt_ = salt() if crypt is None: salt_ = '$apr1$' + salt_ if salt_.startswith('$apr1$'): return md5crypt(password, salt_[6:].split('$')[0], '$apr1$') elif salt_.startswith('{SHA}'): return '{SHA}' + sha.new(password).digest().encode('base64')[:-1] elif crypt is None: # crypt passwords are only supported on Unix-like systems raise NotImplementedError(u'Le module "crypt" n\'est pas disponible ' u'sur cette plateforme.') else: return crypt(password, salt_) def htdigest(user, realm, password): p = ':'.join([user, realm, password]) return md5.new(p).hexdigest()