[2] | 1 | # -*- coding: utf8 -*- |
---|
| 2 | # |
---|
| 3 | # Copyright (C) 2007 Matthew Good <trac@matt-good.net> |
---|
| 4 | # |
---|
| 5 | # "THE BEER-WARE LICENSE" (Revision 42): |
---|
| 6 | # <trac@matt-good.net> wrote this file. As long as you retain this notice you |
---|
| 7 | # can do whatever you want with this stuff. If we meet some day, and you think |
---|
| 8 | # this stuff is worth it, you can buy me a beer in return. Matthew Good |
---|
| 9 | # |
---|
| 10 | # Author: Matthew Good <trac@matt-good.net> |
---|
| 11 | |
---|
| 12 | from binascii import hexlify |
---|
| 13 | import md5, sha |
---|
| 14 | |
---|
| 15 | from trac.core import * |
---|
| 16 | from trac.config import Option |
---|
| 17 | |
---|
| 18 | from md5crypt import md5crypt |
---|
| 19 | |
---|
| 20 | class IPasswordHashMethod(Interface): |
---|
| 21 | def generate_hash(user, password): |
---|
| 22 | pass |
---|
| 23 | |
---|
| 24 | def test_hash(user, password, hash): |
---|
| 25 | pass |
---|
| 26 | |
---|
| 27 | |
---|
| 28 | class HtPasswdHashMethod(Component): |
---|
| 29 | implements(IPasswordHashMethod) |
---|
| 30 | |
---|
| 31 | def generate_hash(self, user, password): |
---|
| 32 | password = password.encode('utf-8') |
---|
| 33 | return htpasswd(password) |
---|
| 34 | |
---|
| 35 | def check_hash(self, user, password, hash): |
---|
| 36 | password = password.encode('utf-8') |
---|
| 37 | return hash == htpasswd(password, hash) |
---|
| 38 | |
---|
| 39 | |
---|
| 40 | class HtDigestHashMethod(Component): |
---|
| 41 | implements(IPasswordHashMethod) |
---|
| 42 | |
---|
| 43 | realm = Option('account-manager', 'htdigest_realm') |
---|
| 44 | |
---|
| 45 | def generate_hash(self, user, password): |
---|
| 46 | user,password,realm = _encode(user, password, self.realm) |
---|
| 47 | return ':'.join([realm, htdigest(user, realm, password)]) |
---|
| 48 | |
---|
| 49 | def check_hash(self, user, password, hash): |
---|
| 50 | user,password,realm = _encode(user, password, self.realm) |
---|
| 51 | return hash == self.generate_hash(user, password) |
---|
| 52 | |
---|
| 53 | |
---|
| 54 | def _encode(*args): |
---|
| 55 | return [a.encode('utf-8') for a in args] |
---|
| 56 | |
---|
| 57 | # check for the availability of the "crypt" module for checking passwords on |
---|
| 58 | # Unix-like platforms |
---|
| 59 | # MD5 is still used when adding/updating passwords |
---|
| 60 | try: |
---|
| 61 | from crypt import crypt |
---|
| 62 | except ImportError: |
---|
| 63 | crypt = None |
---|
| 64 | |
---|
| 65 | # os.urandom was added in Python 2.4 |
---|
| 66 | # try to fall back on reading from /dev/urandom on older Python versions |
---|
| 67 | try: |
---|
| 68 | from os import urandom |
---|
| 69 | except ImportError: |
---|
| 70 | from random import randrange |
---|
| 71 | def urandom(n): |
---|
| 72 | return ''.join([chr(randrange(256)) for _ in xrange(n)]) |
---|
| 73 | |
---|
| 74 | def salt(): |
---|
| 75 | s = '' |
---|
| 76 | v = long(hexlify(urandom(4)), 16) |
---|
| 77 | itoa64 = './0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' |
---|
| 78 | for i in range(8): |
---|
| 79 | s += itoa64[v & 0x3f]; v >>= 6 |
---|
| 80 | return s |
---|
| 81 | |
---|
| 82 | def htpasswd(password, salt_=None): |
---|
| 83 | # TODO need unit test of generating new hash |
---|
| 84 | if salt_ is None: |
---|
| 85 | salt_ = salt() |
---|
| 86 | if crypt is None: |
---|
| 87 | salt_ = '$apr1$' + salt_ |
---|
| 88 | if salt_.startswith('$apr1$'): |
---|
| 89 | return md5crypt(password, salt_[6:].split('$')[0], '$apr1$') |
---|
| 90 | elif salt_.startswith('{SHA}'): |
---|
| 91 | return '{SHA}' + sha.new(password).digest().encode('base64')[:-1] |
---|
| 92 | elif crypt is None: |
---|
| 93 | # crypt passwords are only supported on Unix-like systems |
---|
| 94 | raise NotImplementedError(u'Le module "crypt" n\'est pas disponible ' |
---|
| 95 | u'sur cette plateforme.') |
---|
| 96 | else: |
---|
| 97 | return crypt(password, salt_) |
---|
| 98 | |
---|
| 99 | def htdigest(user, realm, password): |
---|
| 100 | p = ':'.join([user, realm, password]) |
---|
| 101 | return md5.new(p).hexdigest() |
---|