root / trunk / twisted / cred / checkers.py

Revision 24259, 9.1 kB (checked in by exarkun, 1 year ago)

Add a link to the credentials module to the ICredentialsChecker.requestAvatarId docstring

Author: exarkun
Reviewer: jml
Fixes: #3346

The credentials object passed to ICredentialsChecker.requestAvatarId is an object providing
one or more of a number of interfaces; to try to help new users just learning cred, add a
link to the module defining most of Twisted's built in credentials interfaces to that
method's docstring.

Line 
1 # -*- test-case-name: twisted.test.test_newcred -*-
2 # Copyright (c) 2001-2008 Twisted Matrix Laboratories.
3 # See LICENSE for details.
4
5 import os
6
7 from zope.interface import implements, Interface, Attribute
8
9 from twisted.internet import defer
10 from twisted.python import failure, log
11 from twisted.cred import error, credentials
12
13
14
15 class ICredentialsChecker(Interface):
16     """
17     An object that can check sub-interfaces of ICredentials.
18     """
19
20     credentialInterfaces = Attribute(
21         'A list of sub-interfaces of ICredentials which specifies which I may check.')
22
23
24     def requestAvatarId(credentials):
25         """
26         @param credentials: something which implements one of the interfaces in
27         self.credentialInterfaces.
28
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.
33
34         @see: L{twisted.cred.credentials}
35         """
36
37
38
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.
48
49 ANONYMOUS = ()
50
51
52 class AllowAnonymousAccess:
53     implements(ICredentialsChecker)
54     credentialInterfaces = credentials.IAnonymous,
55
56     def requestAvatarId(self, credentials):
57         return defer.succeed(ANONYMOUS)
58
59
60 class InMemoryUsernamePasswordDatabaseDontUse:
61     """
62     An extremely simple credentials checker.
63
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.
66
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}.
70     """
71
72     implements(ICredentialsChecker)
73
74     credentialInterfaces = (credentials.IUsernamePassword,
75                             credentials.IUsernameHashedPassword)
76
77     def __init__(self, **users):
78         self.users = users
79
80     def addUser(self, username, password):
81         self.users[username] = password
82
83     def _cbPasswordMatch(self, matched, username):
84         if matched:
85             return username
86         else:
87             return failure.Failure(error.UnauthorizedLogin())
88
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))
95         else:
96             return defer.fail(error.UnauthorizedLogin())
97
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
112     implements(ICredentialsChecker)
113
114     cache = False
115     _credCache = None
116     _cacheTimestamp = 0
117
118     def __init__(self, filename, delim=':', usernameField=0, passwordField=1,
119                  caseSensitive=True, hash=None, cache=False):
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
132         @type passwordField: C{int}
133         @param passwordField: The index of the password after splitting a
134         line on the delimiter.
135
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
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.
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.
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
160         self.cache = cache
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
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
187     def _cbPasswordMatch(self, matched, username):
188         if matched:
189             return username
190         else:
191             return failure.Failure(error.UnauthorizedLogin())
192
193
194     def _loadCredentials(self):
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:
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
226             raise KeyError(username)
227
228
229     def requestAvatarId(self, c):
230         try:
231             u, p = self.getUser(c.username)
232         except KeyError:
233             return defer.fail(error.UnauthorizedLogin())
234         else:
235             up = credentials.IUsernamePassword(c, None)
236             if self.hash:
237                 if up is not None:
238                     h = self.hash(up.username, up.password, p)
239                     if h == p:
240                         return defer.succeed(u)
241                 return defer.fail(error.UnauthorizedLogin())
242             else:
243                 return defer.maybeDeferred(c.checkPassword, p
244                     ).addCallback(self._cbPasswordMatch, u)
245
246
247
248 class PluggableAuthenticationModulesChecker:
249     implements(ICredentialsChecker)
250     credentialInterfaces = credentials.IPluggableAuthenticationModules,
251     service = 'Twisted'
252
253     def requestAvatarId(self, credentials):
254         try:
255             from twisted.cred import pamauth
256         except ImportError: # PyPAM is missing
257             return defer.fail(error.UnauthorizedLogin())
258         else:
259             d = pamauth.pamAuthenticate(self.service, credentials.username,
260                                         credentials.pamConversion)
261             d.addCallback(lambda x: credentials.username)
262             return d
263
264
265
266 # For backwards compatibility
267 # Allow access as the old name.
268 OnDiskUsernamePasswordDatabase = FilePasswordDB
Note: See TracBrowser for help on using the browser.