2
# Copyright (c) 2001-2004 Twisted Matrix Laboratories.
3
# See LICENSE for details.
8
Base authentication mechanisms for Twisted.
10
Maintainer: U{Glyph Lefkowitz<mailto:glyph@twistedmatrix.com>}
12
Stability: semi-stable
14
Future Plans: There needs to be more pluggable support for different, disparate
15
authentication mechanisms being supported by the same Identity as long as it
16
supports the appropriate persistent data-storage fields. This will likely be
17
accomplished with Adapters and possibly Componentized, although it may just be
18
the addition of more methods in the base Identity.
23
import md5, types, sys, warnings
26
from twisted.python import failure
27
from twisted.internet import defer
30
from util import respond
31
from util import challenge
32
from error import Unauthorized, KeyNotFound
36
"""An identity, with different methods for verification.
38
An identity represents a user's permissions with a particular
39
application. It is a username, a password, and a collection of
40
Perspective/Service name pairs, each of which is a perspective
41
that this identity is allowed to access.
45
def __init__(self, name, authorizer):
46
"""Create an identity.
48
I must have a name, and a backreference to the Application that the
49
Keys on my keyring make reference to.
51
warnings.warn("Identities are deprecated, switch to credentialcheckers etc.",
52
category=DeprecationWarning, stacklevel=2)
53
if not isinstance(name, types.StringType):
55
from twisted.internet import app
56
if isinstance(authorizer, app.Application):
57
authorizer = authorizer.authorizer
59
self.authorizer = authorizer
62
def upgradeToVersion2(self):
63
self.authorizer = self.application.authorizer
66
def addKeyForPerspective(self, perspective):
67
"""Add a key for the given perspective.
69
perspectiveName = perspective.getPerspectiveName()
70
serviceName = perspective.service.getServiceName()
71
self.addKeyByString(serviceName, perspectiveName)
73
def addKeyByString(self, serviceName, perspectiveName):
74
"""Put a key on my keyring.
76
This key will give me a token to access to some service in the
79
self.keyring[(serviceName, perspectiveName)] = 1
81
def requestPerspectiveForService(self, serviceName):
82
"""Get the first available perspective for a given service.
84
keys = self.keyring.keys()
86
for serviceN, perspectiveN in keys:
87
if serviceN == serviceName:
88
return self.requestPerspectiveForKey(serviceName, perspectiveN)
89
return defer.fail("No such perspective.")
91
def requestPerspectiveForKey(self, serviceName, perspectiveName):
92
"""Get a perspective request (a Deferred) for the given key.
94
If this identity does not have access to the given C{(serviceName,
95
perspectiveName)} pair, I will raise L{KeyNotFound<error.KeyNotFound>}.
98
check = self.keyring[(serviceName, perspectiveName)]
100
e = KeyNotFound(serviceName, perspectiveName)
101
return defer.fail(failure.Failure(e, KeyNotFound,
103
return self.authorizer.getServiceNamed(serviceName).getPerspectiveForIdentity(perspectiveName, self)
105
def getAllKeys(self):
106
"""Returns a list of all services and perspectives this identity can connect to.
108
This returns a sequence of keys.
110
return self.keyring.keys()
112
def removeKey(self, serviceName, perspectiveName):
113
"""Remove a key from my keyring.
115
If this key is not present, raise KeyError.
117
del self.keyring[(serviceName, perspectiveName)]
120
"""Persist this Identity to the authorizer.
122
return self.authorizer.addIdentity(self)
124
### Authentication Mechanisms
126
def setPassword(self, plaintext):
127
if plaintext is None:
128
self.hashedPassword = None
130
self.hashedPassword = md5.new(plaintext).digest()
132
def setAlreadyHashedPassword(self, cyphertext):
133
"""(legacy) Set a password for this identity, already md5 hashed.
135
self.hashedPassword = cyphertext
138
"""I return some random data.
140
This is a method in addition to the module-level function
141
because it is anticipated that we will want to change this
142
to store salted passwords.
146
def verifyPassword(self, challenge, hashedPassword):
147
"""Verify a challenge/response password.
149
req = defer.Deferred()
150
if self.hashedPassword is None:
151
# no password was set, so we can't log in
152
req.errback(Unauthorized("account is disabled"))
155
md.update(self.hashedPassword)
157
correct = md.digest()
158
if hashedPassword == correct:
159
req.callback("password verified")
161
req.errback(Unauthorized("incorrect password"))
164
def verifyPlainPassword(self, plaintext):
165
"""Verify plain text password.
167
This is insecure, but necessary to support legacy protocols such
168
as IRC, POP3, HTTP, etc.
170
req = defer.Deferred()
171
if self.hashedPassword is None:
172
# no password was set, so we can't log in
173
req.errback(Unauthorized("account is disabled"))
177
userPass = md.digest()
178
if userPass == self.hashedPassword:
179
req.callback("password verified")
181
req.errback(Unauthorized("incorrect password"))
185
return "<%s %r at 0x%x>" % (self.__class__, self.name, id(self))
188
# TODO: service discovery through listing of self.keyring.