1
# -*- test-case-name: twisted.test.test_newcred -*-
2
# Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3
# See LICENSE for details.
7
from zope.interface import implements, Interface, Attribute
9
from twisted.internet import defer
10
from twisted.python import failure, log
11
from twisted.cred import error, credentials
15
class ICredentialsChecker(Interface):
17
An object that can check sub-interfaces of ICredentials.
20
credentialInterfaces = Attribute(
21
'A list of sub-interfaces of ICredentials which specifies which I may check.')
24
def requestAvatarId(credentials):
26
@param credentials: something which implements one of the interfaces in
27
self.credentialInterfaces.
29
@return: a Deferred which will fire a string which identifies an
30
avatar, an empty tuple to specify an authenticated anonymous user
31
(provided as checkers.ANONYMOUS) or fire a Failure(UnauthorizedLogin).
32
Alternatively, return the result itself.
34
@see: L{twisted.cred.credentials}
39
# A note on anonymity - We do not want None as the value for anonymous
40
# because it is too easy to accidentally return it. We do not want the
41
# empty string, because it is too easy to mistype a password file. For
42
# example, an .htpasswd file may contain the lines: ['hello:asdf',
43
# 'world:asdf', 'goodbye', ':world']. This misconfiguration will have an
44
# ill effect in any case, but accidentally granting anonymous access is a
45
# worse failure mode than simply granting access to an untypeable
46
# username. We do not want an instance of 'object', because that would
47
# create potential problems with persistence.
52
class AllowAnonymousAccess:
53
implements(ICredentialsChecker)
54
credentialInterfaces = credentials.IAnonymous,
56
def requestAvatarId(self, credentials):
57
return defer.succeed(ANONYMOUS)
60
class InMemoryUsernamePasswordDatabaseDontUse:
62
An extremely simple credentials checker.
64
This is only of use in one-off test programs or examples which don't
65
want to focus too much on how credentials are verified.
67
You really don't want to use this for anything else. It is, at best, a
68
toy. If you need a simple credentials checker for a real application,
69
see L{FilePasswordDB}.
72
implements(ICredentialsChecker)
74
credentialInterfaces = (credentials.IUsernamePassword,
75
credentials.IUsernameHashedPassword)
77
def __init__(self, **users):
80
def addUser(self, username, password):
81
self.users[username] = password
83
def _cbPasswordMatch(self, matched, username):
87
return failure.Failure(error.UnauthorizedLogin())
89
def requestAvatarId(self, credentials):
90
if credentials.username in self.users:
91
return defer.maybeDeferred(
92
credentials.checkPassword,
93
self.users[credentials.username]).addCallback(
94
self._cbPasswordMatch, str(credentials.username))
96
return defer.fail(error.UnauthorizedLogin())
100
"""A file-based, text-based username/password database.
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.
112
implements(ICredentialsChecker)
118
def __init__(self, filename, delim=':', usernameField=0, passwordField=1,
119
caseSensitive=True, hash=None, cache=False):
121
@type filename: C{str}
122
@param filename: The name of the file from which to read username and
123
password information.
126
@param delim: The field delimiter used in the file.
128
@type usernameField: C{int}
129
@param usernameField: The index of the username after splitting a
130
line on the delimiter.
132
@type passwordField: C{int}
133
@param passwordField: The index of the password after splitting a
134
line on the delimiter.
136
@type caseSensitive: C{bool}
137
@param caseSensitive: If true, consider the case of the username when
138
performing a lookup. Ignore it otherwise.
140
@type hash: Three-argument callable or C{None}
141
@param hash: A function used to transform the plaintext password
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.
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.
154
self.filename = filename
156
self.ufield = usernameField
157
self.pfield = passwordField
158
self.caseSensitive = caseSensitive
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
170
# The passwords are hashed on disk. We can support only
171
# plaintext passwords received over the network.
172
self.credentialInterfaces = (
173
credentials.IUsernamePassword,
177
def __getstate__(self):
179
for k in '_credCache', '_cacheTimestamp':
187
def _cbPasswordMatch(self, matched, username):
191
return failure.Failure(error.UnauthorizedLogin())
194
def _loadCredentials(self):
196
f = file(self.filename)
199
raise error.UnauthorizedLogin()
203
parts = line.split(self.delim)
205
if self.ufield >= len(parts) or self.pfield >= len(parts):
207
if self.caseSensitive:
208
yield parts[self.ufield], parts[self.pfield]
210
yield parts[self.ufield].lower(), parts[self.pfield]
213
def getUser(self, username):
214
if not self.caseSensitive:
215
username = username.lower()
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]
223
for u, p in self._loadCredentials():
226
raise KeyError(username)
229
def requestAvatarId(self, c):
231
u, p = self.getUser(c.username)
233
return defer.fail(error.UnauthorizedLogin())
235
up = credentials.IUsernamePassword(c, None)
238
h = self.hash(up.username, up.password, p)
240
return defer.succeed(u)
241
return defer.fail(error.UnauthorizedLogin())
243
return defer.maybeDeferred(c.checkPassword, p
244
).addCallback(self._cbPasswordMatch, u)
248
class PluggableAuthenticationModulesChecker:
249
implements(ICredentialsChecker)
250
credentialInterfaces = credentials.IPluggableAuthenticationModules,
253
def requestAvatarId(self, credentials):
255
from twisted.cred import pamauth
256
except ImportError: # PyPAM is missing
257
return defer.fail(error.UnauthorizedLogin())
259
d = pamauth.pamAuthenticate(self.service, credentials.username,
260
credentials.pamConversion)
261
d.addCallback(lambda x: credentials.username)
266
# For backwards compatibility
267
# Allow access as the old name.
268
OnDiskUsernamePasswordDatabase = FilePasswordDB