~certify-web-dev/twisted/certify-trunk

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