3
# Copyright (c) 2001-2009 Twisted Matrix Laboratories.
4
# See LICENSE for details.
7
Simple example of a db checker: define a L{ICredentialsChecker} implementation
8
that deals with a database backend to authenticate a user.
11
from twisted.cred import error
12
from twisted.cred.credentials import IUsernameHashedPassword, IUsernamePassword
13
from twisted.cred.checkers import ICredentialsChecker
14
from twisted.internet.defer import Deferred
16
from zope.interface import implements
19
class DBCredentialsChecker(object):
21
This class checks the credentials of incoming connections
22
against a user table in a database.
24
implements(ICredentialsChecker)
26
def __init__(self, runQuery,
27
query="SELECT username, password FROM user WHERE username = %s",
28
customCheckFunc=None, caseSensitivePasswords=True):
30
@param runQuery: This will be called to get the info from the db.
31
Generally you'd want to create a
32
L{twisted.enterprice.adbapi.ConnectionPool} and pass it's runQuery
33
method here. Otherwise pass a function with the same prototype.
34
@type runQuery: C{callable}
36
@type query: query used to authenticate user.
39
@param customCheckFunc: Use this if the passwords in the db are stored
40
as hashes. We'll just call this, so you can do the checking
41
yourself. It takes the following params:
42
(username, suppliedPass, dbPass) and must return a boolean.
43
@type customCheckFunc: C{callable}
45
@param caseSensitivePasswords: If true requires that every letter in
46
C{credentials.password} is exactly the same case as the it's
47
counterpart letter in the database.
48
This is only relevant if C{customCheckFunc} is not used.
49
@type caseSensitivePasswords: C{bool}
51
self.runQuery = runQuery
52
self.caseSensitivePasswords = caseSensitivePasswords
53
self.customCheckFunc = customCheckFunc
54
# We can't support hashed password credentials if we only have a hash
57
self.credentialInterfaces = (IUsernamePassword,)
59
self.credentialInterfaces = (
60
IUsernamePassword, IUsernameHashedPassword,)
64
def requestAvatarId(self, credentials):
66
Authenticates the kiosk against the database.
68
# Check that the credentials instance implements at least one of our
70
for interface in self.credentialInterfaces:
71
if interface.providedBy(credentials):
74
raise error.UnhandledCredentials()
75
# Ask the database for the username and password
76
dbDeferred = self.runQuery(self.sql, (credentials.username,))
77
# Setup our deferred result
79
dbDeferred.addCallbacks(self._cbAuthenticate, self._ebAuthenticate,
80
callbackArgs=(credentials, deferred),
81
errbackArgs=(credentials, deferred))
84
def _cbAuthenticate(self, result, credentials, deferred):
86
Checks to see if authentication was good. Called once the info has
87
been retrieved from the DB.
90
# Username not found in db
91
deferred.errback(error.UnauthorizedLogin('Username unknown'))
93
username, password = result[0]
94
if self.customCheckFunc:
95
# Let the owner do the checking
96
if self.customCheckFunc(
97
username, credentials.password, password):
98
deferred.callback(credentials.username)
101
error.UnauthorizedLogin('Password mismatch'))
103
# It's up to us or the credentials object to do the checking
105
if IUsernameHashedPassword.providedBy(credentials):
106
# Let the hashed password checker do the checking
107
if credentials.checkPassword(password):
108
deferred.callback(credentials.username)
111
error.UnauthorizedLogin('Password mismatch'))
112
elif IUsernamePassword.providedBy(credentials):
113
# Compare the passwords, deciging whether or not to use
115
if self.caseSensitivePasswords:
117
password.lower() == credentials.password.lower())
119
passOk = password == credentials.password
122
deferred.callback(credentials.username)
125
error.UnauthorizedLogin('Password mismatch'))
127
# OK, we don't know how to check this
128
deferred.errback(error.UnhandledCredentials())
130
def _ebAuthenticate(self, message, credentials, deferred):
132
The database lookup failed for some reason.
134
deferred.errback(error.LoginFailed(message))
139
Run a simple echo pb server to test the checker. It defines a custom query
140
for dealing with sqlite special quoting, but otherwise it's a
141
straightforward use of the object.
143
You can test it running C{pbechoclient.py}.
146
from twisted.python import log
147
log.startLogging(sys.stdout)
149
if os.path.isfile('testcred'):
150
os.remove('testcred')
151
from twisted.enterprise import adbapi
152
pool = adbapi.ConnectionPool('pysqlite2.dbapi2', 'testcred')
153
# Create the table that will be used
154
query1 = """CREATE TABLE user (
159
query2 = """INSERT INTO user VALUES ('guest', 'guest')"""
161
pool.runQuery(query2)
162
pool.runQuery(query1).addCallback(cb)
164
checker = DBCredentialsChecker(pool.runQuery,
165
query="SELECT username, password FROM user WHERE username = ?")
166
from twisted.cred.portal import Portal
169
from twisted.spread import pb
170
portal = Portal(pbecho.SimpleRealm())
171
portal.registerChecker(checker)
172
reactor.listenTCP(pb.portno, pb.PBServerFactory(portal))
175
if __name__ == "__main__":
176
from twisted.internet import reactor
177
reactor.callWhenRunning(main)