~justin-fathomdb/nova/justinsb-openstack-api-volumes

« back to all changes in this revision

Viewing changes to vendor/Twisted-10.0.0/twisted/cred/checkers.py

  • Committer: Jesse Andrews
  • Date: 2010-05-28 06:05:26 UTC
  • Revision ID: git-v1:bf6e6e718cdc7488e2da87b21e258ccc065fe499
initial commit

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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