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