1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
1 |
# -*- test-case-name: twisted.test.test_newcred -*-
|
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
2 |
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
|
3 |
# See LICENSE for details.
|
|
4 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
5 |
from __future__ import generators |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
6 |
|
10
by Matthias Klose
Synchronize with Debian unstable. |
7 |
import os |
8 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
9 |
from zope import interface |
10
by Matthias Klose
Synchronize with Debian unstable. |
10 |
|
11 |
from twisted.internet import defer |
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
12 |
from twisted.python import components, failure, log |
13 |
from twisted.cred import error, credentials |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
14 |
|
15 |
try: |
|
16 |
from twisted.cred import pamauth |
|
17 |
except ImportError: # PyPAM is missing |
|
18 |
pamauth = None |
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
19 |
|
20 |
class ICredentialsChecker(components.Interface): |
|
21 |
"""I check sub-interfaces of ICredentials.
|
|
22 |
||
23 |
@cvar credentialInterfaces: A list of sub-interfaces of ICredentials which
|
|
24 |
specifies which I may check.
|
|
25 |
"""
|
|
26 |
||
27 |
def requestAvatarId(self, credentials): |
|
28 |
"""
|
|
29 |
@param credentials: something which implements one of the interfaces in
|
|
30 |
self.credentialInterfaces.
|
|
31 |
||
32 |
@return: a Deferred which will fire a string which identifies an
|
|
33 |
avatar, an empty tuple to specify an authenticated anonymous user
|
|
34 |
(provided as checkers.ANONYMOUS) or fire a Failure(UnauthorizedLogin).
|
|
35 |
Alternatively, return the result itself.
|
|
36 |
"""
|
|
37 |
||
38 |
# A note on anonymity - We do not want None as the value for anonymous
|
|
39 |
# because it is too easy to accidentally return it. We do not want the
|
|
40 |
# empty string, because it is too easy to mistype a password file. For
|
|
41 |
# example, an .htpasswd file may contain the lines: ['hello:asdf',
|
|
42 |
# 'world:asdf', 'goodbye', ':world']. This misconfiguration will have an
|
|
43 |
# ill effect in any case, but accidentally granting anonymous access is a
|
|
44 |
# worse failure mode than simply granting access to an untypeable
|
|
45 |
# username. We do not want an instance of 'object', because that would
|
|
46 |
# create potential problems with persistence.
|
|
47 |
||
48 |
ANONYMOUS = () |
|
49 |
||
50 |
||
51 |
class AllowAnonymousAccess: |
|
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
52 |
interface.implements(ICredentialsChecker) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
53 |
credentialInterfaces = credentials.IAnonymous, |
54 |
||
55 |
def requestAvatarId(self, credentials): |
|
56 |
return defer.succeed(ANONYMOUS) |
|
57 |
||
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
58 |
components.backwardsCompatImplements(AllowAnonymousAccess) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
59 |
|
60 |
class InMemoryUsernamePasswordDatabaseDontUse: |
|
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
61 |
"""An extremely simple credentials checker.
|
62 |
|
|
63 |
This is only of use in one-off test programs or examples which don't
|
|
64 |
want to focus too much on how credentials are verified.
|
|
65 |
|
|
66 |
You really don't want to use this for anything else. It is, at best, a
|
|
67 |
toy. If you need a simple credentials checker for a real application,
|
|
68 |
see L{FilePasswordDB}.
|
|
69 |
"""
|
|
70 |
||
71 |
interface.implements(ICredentialsChecker) |
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
72 |
|
73 |
credentialInterfaces = (credentials.IUsernamePassword, |
|
74 |
credentials.IUsernameHashedPassword) |
|
75 |
||
76 |
def __init__(self, **users): |
|
77 |
self.users = users |
|
78 |
||
79 |
def addUser(self, username, password): |
|
80 |
self.users[username] = password |
|
81 |
||
82 |
def _cbPasswordMatch(self, matched, username): |
|
83 |
if matched: |
|
84 |
return username |
|
85 |
else: |
|
86 |
return failure.Failure(error.UnauthorizedLogin()) |
|
87 |
||
88 |
def requestAvatarId(self, credentials): |
|
89 |
if credentials.username in self.users: |
|
90 |
return defer.maybeDeferred( |
|
91 |
credentials.checkPassword, |
|
92 |
self.users[credentials.username]).addCallback( |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
93 |
self._cbPasswordMatch, str(credentials.username)) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
94 |
else: |
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
95 |
return defer.fail(error.UnauthorizedLogin()) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
96 |
|
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
97 |
components.backwardsCompatImplements(InMemoryUsernamePasswordDatabaseDontUse) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
98 |
|
99 |
class FilePasswordDB: |
|
100 |
"""A file-based, text-based username/password database.
|
|
101 |
||
102 |
Records in the datafile for this class are delimited by a particular
|
|
103 |
string. The username appears in a fixed field of the columns delimited
|
|
104 |
by this string, as does the password. Both fields are specifiable. If
|
|
105 |
the passwords are not stored plaintext, a hash function must be supplied
|
|
106 |
to convert plaintext passwords to the form stored on disk and this
|
|
107 |
CredentialsChecker will only be able to check IUsernamePassword
|
|
108 |
credentials. If the passwords are stored plaintext,
|
|
109 |
IUsernameHashedPassword credentials will be checkable as well.
|
|
110 |
"""
|
|
111 |
||
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
112 |
interface.implements(ICredentialsChecker) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
113 |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
114 |
cache = False |
115 |
_credCache = None |
|
116 |
_cacheTimestamp = 0 |
|
117 |
||
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
118 |
def __init__(self, filename, delim=':', usernameField=0, passwordField=1, |
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
119 |
caseSensitive=True, hash=None, cache=False): |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
120 |
"""
|
121 |
@type filename: C{str}
|
|
122 |
@param filename: The name of the file from which to read username and
|
|
123 |
password information.
|
|
124 |
||
125 |
@type delim: C{str}
|
|
126 |
@param delim: The field delimiter used in the file.
|
|
127 |
||
128 |
@type usernameField: C{int}
|
|
129 |
@param usernameField: The index of the username after splitting a
|
|
130 |
line on the delimiter.
|
|
131 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
132 |
@type passwordField: C{int}
|
133 |
@param passwordField: The index of the password after splitting a
|
|
134 |
line on the delimiter.
|
|
135 |
||
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
136 |
@type caseSensitive: C{bool}
|
137 |
@param caseSensitive: If true, consider the case of the username when
|
|
138 |
performing a lookup. Ignore it otherwise.
|
|
139 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
140 |
@type hash: Three-argument callable or C{None}
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
141 |
@param hash: A function used to transform the plaintext password
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
142 |
received over the network to a format suitable for comparison
|
143 |
against the version stored on disk. The arguments to the callable
|
|
144 |
are the username, the network-supplied password, and the in-file
|
|
145 |
version of the password. If the return value compares equal to the
|
|
146 |
version stored on disk, the credentials are accepted.
|
|
147 |
||
148 |
@type cache: C{bool}
|
|
149 |
@param cache: If true, maintain an in-memory cache of the
|
|
150 |
contents of the password file. On lookups, the mtime of the
|
|
151 |
file will be checked, and the file will only be re-parsed if
|
|
152 |
the mtime is newer than when the cache was generated.
|
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
153 |
"""
|
154 |
self.filename = filename |
|
155 |
self.delim = delim |
|
156 |
self.ufield = usernameField |
|
157 |
self.pfield = passwordField |
|
158 |
self.caseSensitive = caseSensitive |
|
159 |
self.hash = hash |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
160 |
self.cache = cache |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
161 |
|
162 |
if self.hash is None: |
|
163 |
# The passwords are stored plaintext. We can support both
|
|
164 |
# plaintext and hashed passwords received over the network.
|
|
165 |
self.credentialInterfaces = ( |
|
166 |
credentials.IUsernamePassword, |
|
167 |
credentials.IUsernameHashedPassword |
|
168 |
)
|
|
169 |
else: |
|
170 |
# The passwords are hashed on disk. We can support only
|
|
171 |
# plaintext passwords received over the network.
|
|
172 |
self.credentialInterfaces = ( |
|
173 |
credentials.IUsernamePassword, |
|
174 |
)
|
|
175 |
||
176 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
177 |
def __getstate__(self): |
178 |
d = dict(vars(self)) |
|
179 |
for k in '_credCache', '_cacheTimestamp': |
|
180 |
try: |
|
181 |
del d[k] |
|
182 |
except KeyError: |
|
183 |
pass
|
|
184 |
return d |
|
185 |
||
186 |
||
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
187 |
def _cbPasswordMatch(self, matched, username): |
188 |
if matched: |
|
189 |
return username |
|
190 |
else: |
|
191 |
return failure.Failure(error.UnauthorizedLogin()) |
|
192 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
193 |
|
194 |
def _loadCredentials(self): |
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
195 |
try: |
196 |
f = file(self.filename) |
|
197 |
except: |
|
198 |
log.err() |
|
199 |
raise error.UnauthorizedLogin() |
|
200 |
else: |
|
201 |
for line in f: |
|
202 |
line = line.rstrip() |
|
203 |
parts = line.split(self.delim) |
|
204 |
||
205 |
if self.ufield >= len(parts) or self.pfield >= len(parts): |
|
206 |
continue
|
|
207 |
if self.caseSensitive: |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
208 |
yield parts[self.ufield], parts[self.pfield] |
209 |
else: |
|
210 |
yield parts[self.ufield].lower(), parts[self.pfield] |
|
211 |
||
212 |
||
213 |
def getUser(self, username): |
|
214 |
if not self.caseSensitive: |
|
215 |
username = username.lower() |
|
216 |
||
217 |
if self.cache: |
|
218 |
if self._credCache is None or os.path.getmtime(self.filename) > self._cacheTimestamp: |
|
219 |
self._cacheTimestamp = os.path.getmtime(self.filename) |
|
220 |
self._credCache = dict(self._loadCredentials()) |
|
221 |
return username, self._credCache[username] |
|
222 |
else: |
|
223 |
for u, p in self._loadCredentials(): |
|
224 |
if u == username: |
|
225 |
return u, p |
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
226 |
raise KeyError(username) |
227 |
||
228 |
||
229 |
def requestAvatarId(self, c): |
|
230 |
try: |
|
231 |
u, p = self.getUser(c.username) |
|
232 |
except KeyError: |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
233 |
return defer.fail(error.UnauthorizedLogin()) |
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
234 |
else: |
235 |
up = credentials.IUsernamePassword(c, default=None) |
|
236 |
if self.hash: |
|
237 |
if up is not None: |
|
238 |
h = self.hash(up.username, up.password, p) |
|
239 |
if h == p: |
|
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
240 |
return defer.succeed(u) |
241 |
return defer.fail(error.UnauthorizedLogin()) |
|
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
242 |
else: |
243 |
return defer.maybeDeferred(c.checkPassword, p |
|
244 |
).addCallback(self._cbPasswordMatch, u) |
|
1.1.2
by Matthias Klose
Import upstream version 2.0.1 |
245 |
components.backwardsCompatImplements(FilePasswordDB) |
246 |
||
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
247 |
class PluggableAuthenticationModulesChecker: |
248 |
interface.implements(ICredentialsChecker) |
|
249 |
credentialInterfaces = credentials.IPluggableAuthenticationModules, |
|
250 |
service = 'Twisted' |
|
251 |
||
252 |
def requestAvatarId(self, credentials): |
|
253 |
if not pamauth: |
|
10
by Matthias Klose
Synchronize with Debian unstable. |
254 |
return defer.fail(error.UnauthorizedLogin()) |
1.1.3
by Matthias Klose
Import upstream version 2.1.0 |
255 |
d = pamauth.pamAuthenticate(self.service, credentials.username, |
256 |
credentials.pamConversion) |
|
257 |
d.addCallback(lambda x: credentials.username) |
|
258 |
return d |
|
259 |
||
260 |
components.backwardsCompatImplements(PluggableAuthenticationModulesChecker) |
|
261 |
||
1.1.1
by Matthias Klose
Import upstream version 1.3.0 |
262 |
# For backwards compatibility
|
263 |
# Allow access as the old name.
|
|
264 |
OnDiskUsernamePasswordDatabase = FilePasswordDB |