Commit | Line | Data |
---|---|---|
c638d827 CR |
1 | # |
2 | # Copyright (c) 2001 Bizar Software Pty Ltd (http://www.bizarsoftware.com.au/) | |
3 | # This module is free software, and you may redistribute it and/or modify | |
4 | # under the same terms as Python, so long as this copyright message and | |
5 | # disclaimer are retained in their original form. | |
6 | # | |
7 | # IN NO EVENT SHALL BIZAR SOFTWARE PTY LTD BE LIABLE TO ANY PARTY FOR | |
8 | # DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING | |
9 | # OUT OF THE USE OF THIS CODE, EVEN IF THE AUTHOR HAS BEEN ADVISED OF THE | |
10 | # POSSIBILITY OF SUCH DAMAGE. | |
11 | # | |
12 | # BIZAR SOFTWARE PTY LTD SPECIFICALLY DISCLAIMS ANY WARRANTIES, INCLUDING, | |
13 | # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS | |
14 | # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" | |
15 | # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, | |
16 | # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. | |
17 | # | |
18 | # $Id: password.py,v 1.15 2005-12-25 15:38:40 a1s Exp $ | |
19 | ||
20 | """Password handling (encoding, decoding). | |
21 | """ | |
22 | __docformat__ = 'restructuredtext' | |
23 | ||
24 | import re, string, random | |
25 | from roundup.anypy.hashlib_ import md5, sha1 | |
26 | try: | |
27 | import crypt | |
28 | except ImportError: | |
29 | crypt = None | |
30 | ||
31 | class PasswordValueError(ValueError): | |
32 | """ The password value is not valid """ | |
33 | pass | |
34 | ||
35 | def encodePassword(plaintext, scheme, other=None): | |
36 | """Encrypt the plaintext password. | |
37 | """ | |
38 | if plaintext is None: | |
39 | plaintext = "" | |
40 | if scheme == 'SHA': | |
41 | s = sha1(plaintext).hexdigest() | |
42 | elif scheme == 'MD5': | |
43 | s = md5(plaintext).hexdigest() | |
44 | elif scheme == 'crypt' and crypt is not None: | |
45 | if other is not None: | |
46 | salt = other | |
47 | else: | |
48 | saltchars = './0123456789'+string.letters | |
49 | salt = random.choice(saltchars) + random.choice(saltchars) | |
50 | s = crypt.crypt(plaintext, salt) | |
51 | elif scheme == 'plaintext': | |
52 | s = plaintext | |
53 | else: | |
54 | raise PasswordValueError, 'unknown encryption scheme %r'%scheme | |
55 | return s | |
56 | ||
57 | def generatePassword(length=8): | |
58 | chars = string.letters+string.digits | |
59 | return ''.join([random.choice(chars) for x in range(length)]) | |
60 | ||
61 | class Password: | |
62 | """The class encapsulates a Password property type value in the database. | |
63 | ||
64 | The encoding of the password is one if None, 'SHA', 'MD5' or 'plaintext'. | |
65 | The encodePassword function is used to actually encode the password from | |
66 | plaintext. The None encoding is used in legacy databases where no | |
67 | encoding scheme is identified. | |
68 | ||
69 | The scheme is stored with the encoded data in the database: | |
70 | {scheme}data | |
71 | ||
72 | Example usage: | |
73 | >>> p = Password('sekrit') | |
74 | >>> p == 'sekrit' | |
75 | 1 | |
76 | >>> p != 'not sekrit' | |
77 | 1 | |
78 | >>> 'sekrit' == p | |
79 | 1 | |
80 | >>> 'not sekrit' != p | |
81 | 1 | |
82 | """ | |
83 | ||
84 | default_scheme = 'SHA' # new encryptions use this scheme | |
85 | pwre = re.compile(r'{(\w+)}(.+)') | |
86 | ||
87 | def __init__(self, plaintext=None, scheme=None, encrypted=None): | |
88 | """Call setPassword if plaintext is not None.""" | |
89 | if scheme is None: | |
90 | scheme = self.default_scheme | |
91 | if plaintext is not None: | |
92 | self.setPassword (plaintext, scheme) | |
93 | elif encrypted is not None: | |
94 | self.unpack(encrypted, scheme) | |
95 | else: | |
96 | self.scheme = self.default_scheme | |
97 | self.password = None | |
98 | self.plaintext = None | |
99 | ||
100 | def unpack(self, encrypted, scheme=None): | |
101 | """Set the password info from the scheme:<encryted info> string | |
102 | (the inverse of __str__) | |
103 | """ | |
104 | m = self.pwre.match(encrypted) | |
105 | if m: | |
106 | self.scheme = m.group(1) | |
107 | self.password = m.group(2) | |
108 | self.plaintext = None | |
109 | else: | |
110 | # currently plaintext - encrypt | |
111 | self.setPassword(encrypted, scheme) | |
112 | ||
113 | def setPassword(self, plaintext, scheme=None): | |
114 | """Sets encrypts plaintext.""" | |
115 | if scheme is None: | |
116 | scheme = self.default_scheme | |
117 | self.scheme = scheme | |
118 | self.password = encodePassword(plaintext, scheme) | |
119 | self.plaintext = plaintext | |
120 | ||
121 | def __cmp__(self, other): | |
122 | """Compare this password against another password.""" | |
123 | # check to see if we're comparing instances | |
124 | if isinstance(other, Password): | |
125 | if self.scheme != other.scheme: | |
126 | return cmp(self.scheme, other.scheme) | |
127 | return cmp(self.password, other.password) | |
128 | ||
129 | # assume password is plaintext | |
130 | if self.password is None: | |
131 | raise ValueError, 'Password not set' | |
132 | return cmp(self.password, encodePassword(other, self.scheme, | |
133 | self.password)) | |
134 | ||
135 | def __str__(self): | |
136 | """Stringify the encrypted password for database storage.""" | |
137 | if self.password is None: | |
138 | raise ValueError, 'Password not set' | |
139 | return '{%s}%s'%(self.scheme, self.password) | |
140 | ||
141 | def test(): | |
142 | # SHA | |
143 | p = Password('sekrit') | |
144 | assert p == 'sekrit' | |
145 | assert p != 'not sekrit' | |
146 | assert 'sekrit' == p | |
147 | assert 'not sekrit' != p | |
148 | ||
149 | # MD5 | |
150 | p = Password('sekrit', 'MD5') | |
151 | assert p == 'sekrit' | |
152 | assert p != 'not sekrit' | |
153 | assert 'sekrit' == p | |
154 | assert 'not sekrit' != p | |
155 | ||
156 | # crypt | |
157 | p = Password('sekrit', 'crypt') | |
158 | assert p == 'sekrit' | |
159 | assert p != 'not sekrit' | |
160 | assert 'sekrit' == p | |
161 | assert 'not sekrit' != p | |
162 | ||
163 | if __name__ == '__main__': | |
164 | test() | |
165 | ||
166 | # vim: set filetype=python sts=4 sw=4 et si : |