[gregjarman@yahoo.com: Re: [ZODB-Dev] SecureServerStorage and SecureClientStorage]
Christian Reis
kiko@async.com.br
Mon, 3 Mar 2003 15:08:16 -0300
By the way, I was reading through the 900 emails I have accumulated over
the past 4 weeks, and I remembered this one I had from long ago.
The AuthZEO implementation is a bit cleaner, but I'd like to point out
that Greg offers an authentication object instead of a username/password
pair. This could allow us to eventually (the example below doesn't) use
an extended API that goes beyond username/password, but it has a big
impact on both the password database and the authentication modules.
I think this is overkill, but maybe somebody else has a different
opinion. My vote is YAGNI.
----- Forwarded message from Greg Jarman <gregjarman@yahoo.com> -----
From: Greg Jarman <gregjarman@yahoo.com>
Subject: Re: [ZODB-Dev] SecureServerStorage and SecureClientStorage
To: jeremy@alum.mit.edu, Christian Reis <kiko@async.com.br>
Cc: Johan Dahlin <jdahlin@telia.com>, zodb-dev@zope.org
Date: Wed, 9 Oct 2002 10:12:11 -0700 (PDT)
>>>>> "CR" == Christian Reis <kiko@async.com.br> writes:
CR> Does anybody (*wink*) have the time to look and maybe suggest
CR> some changes so we could implement something generically useful
CR> and acceptable for inclusion?
I added a simple authenticator to the Standalone distribution several
months ago. It authenticates both the client to the server and the
server to the client using a chap-like method (the passwords are never
sent across the network).
To test it:
1. Patch StorageServer.py and ClientStorage.py
2. Modify your zeo server startup (probably start.py?) to pass a
ZEOAuthentication.ServerAuthenticator object to the StorageServer
constructor.
3. Modify your client to pass a ZEOAuthentication.ClientAuthenticator
object to the ClientStorage constructor (using the auth= keyword
argument if necessary).
4. Cross your fingers...
I hope yahoo mail doesn't mess up the formatting...
Cheers
Greg
--- StorageServer.patch
--- StorageServer.py.orig 2002-10-09 16:52:15.000000000 +0100
+++ StorageServer.py 2002-10-09 16:59:03.000000000 +0100
@@ -128,7 +128,7 @@
class StorageServer(asyncore.dispatcher):
- def __init__(self, connection, storages):
+ def __init__(self, connection, storages, auth=None):
self.__storages=storages
for n, s in storages.items():
@@ -136,6 +136,7 @@
self.__connections={}
self.__get_connections=self.__connections.get
+ self._authenticator = auth
self._pack_trigger = trigger.trigger()
asyncore.dispatcher.__init__(self)
@@ -216,6 +217,7 @@
'tpc_finish', 'undo', 'undoLog', 'undoInfo', 'versionEmpty',
'versions',
'transactionalUndo',
'vote', 'zeoLoad', 'zeoVerify', 'beginZeoVerify', 'endZeoVerify',
+ 'require_auth', 'auth_get_info', 'auth_client',
):
storage_methods[n]=1
storage_method=storage_methods.has_key
@@ -247,6 +249,7 @@
self.__server=server
self.__invalidated=[]
self.__closed=None
+ self._authenticated = 0
if __debug__: debug='ZEO Server'
else: debug=0
SizedMessageAsyncConnection.__init__(self, sock, addr,
debug=debug)
@@ -296,6 +299,12 @@
apply(blather,
("call", id(self), ":", name,) + args)
+ if self.__server._authenticator is not None and \
+ not self._authenticated and not
name.startswith('auth'):
+ # Perhaps we should raise an exception here, but lets
just
+ # silently ignore it for now.
+ return
+
if not storage_method(name):
raise 'Invalid Method Name', name
if hasattr(self, name):
@@ -334,6 +343,31 @@
self.message_output('E'+r)
+ def require_auth(self, username, message):
+ if self.__server._authenticator is not None:
+ return self.__server._authenticator.encrypt(self.addr,
username, \
+ message)
+ else:
+ return _noreturn
+
+ def auth_get_info(self):
+ if self.__server._authenticator is not None:
+ self.__authentication_message = \
+ self.__server._authenticator.generateMessage()
+ return self.__server._authenticator.getUsername(), \
+ self.__authentication_message
+ else:
+ return _noreturn
+
+ def auth_client(self, digest):
+ if self.__server._authenticator is not None:
+ if self.__server._authenticator.compare( \
+ self.__server._authenticator.getUsername(),
+ self.__authentication_message,
+ digest):
+ self._authenticated = 1
+ else:
+ raise Exception, "Authentication failed"
def get_info(self):
storage=self.__storage
@@ -544,6 +578,7 @@
storage.tpc_begin(t)
self.__invalidated=[]
+
def tpc_begin_sync(self, id, user, description, ext):
if self.__closed: return
t=self._transaction
--- ClientStorage.patch
--- ClientStorage.py.orig 2002-10-09 16:51:53.000000000 +0100
+++ ClientStorage.py 2002-10-09 16:57:40.000000000 +0100
@@ -76,6 +76,7 @@
self._oids=[]
self._serials=[]
self._seriald={}
+ self._authenticator = auth
ClientStorage.inheritedAttribute('__init__')(self, name)
@@ -179,6 +180,22 @@
self._call.sendMessage('zeoVerify', oid, s, vs)
self._call.sendMessage('endZeoVerify')
+ if self._authenticator is not None:
+ username, message = self._call('auth_get_info')
+ if username != "":
+ # this server requires authentication
+ self._call('auth_client', \
+ self._authenticator.encrypt(None, username,
message))
+
+ if self._authenticator is not None:
+ message = self._authenticator.generateMessage()
+ digest = self._call('require_auth', \
+ self._authenticator.getUsername(), message)
+ if self._authenticator.compare( \
+ self._authenticator.getUsername(), message, \
+ digest) == 0:
+ raise Exception, "Server failed to authenticate"
+
finally: self._lock_release()
if self._async:
--- ZEOAuthentication.py
import sha
import anydbm
import string
import whrandom
class SHAAuthenticator:
def encrypt(self, addr, username, message):
s = sha.new()
s.update(self.getPasswordForUser(username))
s.update(message)
return s.hexdigest()
def compare(self, username, message, digest):
s = sha.new()
s.update(self.getPasswordForUser(username))
s.update(message)
return digest == s.hexdigest()
def generateMessage(self):
"""Borrowed from the ASPN Python cookbook"""
chars = string.letters + string.digits
passwd = ""
# determine password size (randomly, but between the given
range)
passwd_size = whrandom.randint(8, 15)
for x in range(passwd_size):
# choose a random alpha-numeric character
passwd += whrandom.choice(chars)
return passwd
class ServerAuthenticator(SHAAuthenticator):
"""Example server-side authenticator."""
def __init__(self, username, passwordfile):
self._username = 'server'
self._passwords = { 'server': 'server-password', \
'user': 'user-password' }
def getUsername(self):
"""Return our username"""
return self._username
def getPasswordForUser(self, username):
return self._passwords[username]
class ClientAuthenticator(SHAAuthenticator):
"""Example client-side authenticator. You could over-ride these
methods
to use a GUI to prompt for the password"""
def __init__(self):
self._username = None
self._passwords = {}
pass
def getUsername(self):
"""Return our username"""
while self._username is None or len(self._username) == 0:
self._username = raw_input("Enter username: ")
return self._username
def getPasswordForUser(self, username):
if not self._passwords.has_key(username):
self._passwords[username] = None
while self._passwords[username] is None:
self._passwords[username] = \
raw_input("Enter password for " + username + ": ")
return self._passwords[username]
__________________________________________________
Do you Yahoo!?
Faith Hill - Exclusive Performances, Videos & More
http://faith.yahoo.com
_______________________________________________
For more information about ZODB, see the ZODB Wiki:
http://www.zope.org/Wikis/ZODB/
ZODB-Dev mailing list - ZODB-Dev@zope.org
http://lists.zope.org/mailman/listinfo/zodb-dev
----- End forwarded message -----
Take care,
--
Christian Reis, Senior Engineer, Async Open Source, Brazil.
http://async.com.br/~kiko/ | [+55 16] 261 2331 | NMFL