[Zodb-checkins] CVS: Packages/ZEO - Validators.py:1.1.2.1 list_cache.py:1.1.2.1 srp.py:1.1.2.1 srpdemo.py:1.1.2.1 ClientCache.py:1.7.2.1 ClientStorage.py:1.11.2.1 StorageServer.py:1.11.2.1
Jim Fulton
jim@zope.com
Mon, 7 Jan 2002 16:20:32 -0500
Update of /cvs-repository/Packages/ZEO
In directory cvs.zope.org:/tmp/cvs-serv15436
Modified Files:
Tag: ZEO-Auth-Dev
ClientCache.py ClientStorage.py StorageServer.py
Added Files:
Tag: ZEO-Auth-Dev
Validators.py list_cache.py srp.py srpdemo.py
Log Message:
Checking in some old attempts at adding authentication to ZEO
for posterity. In particular, there's a reasonable clean
SRP implementation here.
=== Added File Packages/ZEO/Validators.py ===
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Revision information: $Id: Validators.py,v 1.1.2.1 2002/01/07 21:20:31 jim Exp $
"""
"""Objects that validate connections
"""
class HostValidator:
def __init__(self, storage, allow=None, read_only=0):
self._storage=storage
self.__allow=allow
self.__ro=read_only
def validate(storage_id, server, connection):
"""Try to validate a connection"""
# Need code to check connection address against allow list!
server.register_storage(
connection, self._storage,
self.__ro and StorageServer.read_storage_method or storage_method
)
class SRPValidator:
def __init__(self, storage, allow=None, read_only=0,
hash=None, n=None, g=None, rg=None):
self._storage=storage
self.__allow=allow
self.__ro=read_only
def validate(storage_id, server, connection):
class SRPConnectionValidator:
def __init__(self, validator, server, connection):
self._validator=validator
self.server=server
=== Added File Packages/ZEO/list_cache.py ===
try: import ZODB
except:
import sys
sys.path.append('..')
from ZODB.utils import U64
from ZODB.TimeStamp import TimeStamp
from struct import unpack
class list:
def __init__(self, f):
if type(f) is type(''): f=open(f,'rb')
f.read(4) # header
self.f=f
self.i=-1
self.r='','','','','',''
def read_record(self):
read=self.f.read
h=read(27)
if len(h)==27 and h[8] in 'vni':
tlen, vlen, dlen = unpack(">iHi", h[9:19])
else: return
if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen:
return
oid=h[:8]
serial=TimeStamp(h[19:27])
data=read(dlen)
if vlen:
version = read(vlen)
vdlen=read(4)
if len(vdlen) != 4: return
vdlen=unpack(">i", vdlen)[0]
if vlen+dlen+42+vdlen > tlen: return
vdata=read(vdlen)
vs=TimeStamp(read(8))
else:
vs=None
vdata=version=''
if read(4) != h[9:13]: return
return oid, status, serial, data, version, vs, vdata
def __getitem__(self, i):
if not self.r or i < self.i: raise IndexError, i
while i > self.i:
self.r = self.read_record()
self.i=self.i+1
if not self.r: raise IndexError, i
return self.r
def list_cache(f):
for oid, status, serial, data, version, vs, vdata in list(f):
print oid, status, serial, version, vs
if __name__=='__main__':
import sys
list_cache(sys.argv[1])
=== Added File Packages/ZEO/srp.py ===
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Revision information: $Id: srp.py,v 1.1.2.1 2002/01/07 21:20:31 jim Exp $
"""
"""Simplest possible SRP implementation
Usage:
- Create a 'SRPConfig' object with same given parameters for clients
and server.
- On server, for each user, with user name 'U' and password, 'p', call
'user_info' on the configuration object passing the password to get
a user salt, 's', and a verifier, 'v'. Save the salt and verifier for
the user name.
- When a client wants to connect, it creates a 'User' object, passing
in the configuration, user name, and password. It calls 'session'
on the 'User' object to get the user name, 'U', and a temporary public
key, 'A'. It sends 'U' and 'A' to the server.
- On the server, the user name, 'U', is used to look up the salt, 's',
and verifier, 'v'. A Host object is created by passing 'U', 's', 'v', and
'A'. The 'session' method is called on the 'Host' object to get the
salt, 's', a temporary public key, 'B', and a random number, 'u'. These
are send to the client.
- On the client, the 'key' method is called on the 'User' object,
passing 's', 'B', and 'u' received from the server. A shared session
key, 'K', and client proof, 'M', are returned. The client proof is
sent to the server.
- On the server, the client proof is validated by passing it to the
'validate' method on the 'Host' object. The 'validate' method
returns the shared session key, 'K', and the host proof, which is
sent to the client.
- On the client, the host proof is validated by passing it to the
'validate' method on the 'User' object.
The shared session key may be used to encrypt communication between
the client and host using a symetric key encryption algorithm.
"""
LongType=type(0L)
class SRPConfig:
"""Hold an SRP configuration.
"""
def __init__(self, h, g, N, random):
"""Create an SRP configuration:
h -- A one-way has function that accepts a
string and returns a string.
g -- A (long) generator modulo N
N -- A large safe prime (N = 2q+1, where q is prime)
All arithmetic is done modulo N.
random -- A t-bit random (long) number generator.
"""
self.h, self.g, self.N, self.random = (
h, g, N, random) # algorthm info
self.ngh=stol(h(str(N))) ^ stol(h(str(g)))
def H(self, *args):
hash=self.h
l=list(args)
h=''
while l: h=hash(str(l.pop())+h)
return h
def M(self, s, U, A, B, K):
return self.H(self.ngh, self.H(U), s, A, B, K)
def user_info(self, p, s=None):
"""Get the password verifier and user salt for server
"""
if s is None: s=self.random()
x=stol(self.H(s, p))
v=pow(self.g, x, self.N)
return s, v
def stol(s):
# Convert the bytes in a string to a long.
r=0L
for c in s: r=r*256+ord(c)
return r
class User:
"""Provide user side of SRP computations
"""
def __init__(self, config, U, p):
self.config=config
self.user=U, p
def session(self):
U, p = self.user
a=self.a=self.config.random()
A=self.A=pow(self.config.g, a, self.config.N)
return U, A
def key(self, s, B, u):
if (B%self.config.N)==0:
raise ValueError, B
if u==0: raise ValueError, u
U, p = self.user
N=self.config.N
x=stol(self.config.H(s, p))
v=pow(self.config.g, x, N)
S=pow(B-v, self.a+u*x, N)
self.K=K=self.config.H(S)
self.M=M=self.config.M(s, U, self.A, B, K)
return K, M
def validate(self, proof):
if proof != self.config.H(self.A, self.M, self.K):
raise ValueError, proof
class Host:
"""Provide host side of SRP computations.
"""
def __init__(self, config, U, s, v, A):
self.config = config
if A <= 0 or A >= self.config.N: raise ValueError, A
self.user = U, s, v, A
def session(self):
U, s, v, A = self.user
N=self.config.N
b=self.b=self.config.random()
B=self.B=v+pow(self.config.g, b, N)
u=self.u=self.config.random()
S=pow(A*pow(v, u, N), b, N)
K=self.K=self.config.H(S)
return s, self.B, self.u
def validate(self, proof):
U, s, v, A = self.user
K=self.K
M=self.config.M(s, U, A, self.B, K)
if M != proof: raise ValueError, (proof, M)
return K, self.config.H(A, M, K)
=== Added File Packages/ZEO/srpdemo.py ===
##############################################################################
#
# Copyright (c) 2001 Zope Corporation and Contributors. All Rights Reserved.
#
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL). A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
#
##############################################################################
"""
Revision information: $Id: srpdemo.py,v 1.1.2.1 2002/01/07 21:20:31 jim Exp $
"""
import srp, sha, time, random
def h(v):
s=sha.new()
s.update(v)
return s.digest()
def r(randint=random.randint):
r=long(randint(0,1<<30))
for i in 1,2,3,4,5,6:
r = (r << 24) + randint(0,1<<30)
return r
def r_(randint=random.randint):
return long(randint(0,1<<30))
c=srp.SRPConfig(
N=137656596376486790043182744734961384933899167257744121335064027192370741112305920493080254690601316526576747330553110881621319493425219214435734356437905637147670206858858966652975541347966997276817657605917471296442404150473520316654025988200256062845025470327802138620845134916799507318209468806715548156999L,
g=8623462398472349872L,
h=h,
random=r,
)
U='jim'
p='zope rools and I know it'
s, v = c.user_info(p)
#print "U, p, s, x, v: ", U, p, s, v
user=srp.User(c, U, p)
U, A = user.session()
#print 'U, A: ', U, A
host=srp.Host(c, U, s, v, A)
s, B, u = host.session()
#print "s, B, u: ", s, B, u
#print 'K', `host.K`
K, M = user.key(s, B, u)
#print 'K==K', host.K==K
#print "K, M: ", `K`, `M`
K, host_proof = host.validate(M)
#print "host_proof:", `host_proof`
user.validate(host_proof)
=== Packages/ZEO/ClientCache.py 1.7 => 1.7.2.1 ===
oid -- 8-byte object id
- status -- 1-byte status v': valid, 'n': non-version valid, 'i': invalid
+ status -- 1-byte status 'v': valid, 'n': non-version valid, 'i': invalid
tlen -- 4-byte (unsigned) record length
@@ -108,7 +108,7 @@
vdata -- version data (if vlen > 0)
- vserial -- 8-byte version serial (timestamp)
+ vserial -- 8-byte version serial (timestamp) (if vlen > 0)
tlen -- 4-byte (unsigned) record length (for redundancy and backward
traversal)
@@ -240,20 +240,27 @@
if len(h)==27 and h[8] in 'nv' and h[:8]==oid:
tlen, vlen, dlen = unpack(">iHi", h[9:19])
else: tlen=-1
- if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen:
+ if tlen <= 0 or vlen < 0 or dlen < 0 or vlen+dlen > tlen:
del self._index[oid]
return None
- if version and h[8]=='n': return None
+ if h[8]=='n':
+ if version: return None
+ if not dlen:
+ del self._index[oid]
+ return None
if not vlen or not version:
- return read(dlen), h[19:]
+ if dlen: return read(dlen), h[19:]
+ else: return None
- seek(dlen, 1)
+ if dlen: seek(dlen, 1)
v=read(vlen)
- if version != v:
- seek(-dlen-vlen, 1)
- return read(dlen), h[19:]
+ if version != v:
+ if dlen:
+ seek(-dlen-vlen, 1)
+ return read(dlen), h[19:]
+ else: None
dlen=unpack(">i", read(4))[0]
return read(dlen), read(8)
@@ -262,7 +269,8 @@
if version:
# We need to find and include non-version data
p=self._get(oid, None)
- if p is None: return None
+ if p is None:
+ return self.store(oid, '', '', version, data, serial)
f=self._f[p < 0]
ap=abs(p)
seek=f.seek
@@ -271,13 +279,17 @@
h=read(27)
if len(h)==27 and h[8] in 'nv' and h[:8]==oid:
tlen, vlen, dlen = unpack(">iHi", h[9:19])
- else: tlen=-1
+ else:
+ return self.store(oid, '', '', version, data, serial)
+
if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen:
- del self._index[oid]
- return None
+ return self.store(oid, '', '', version, data, serial)
- p=read(dlen)
- s=h[19:]
+ if dlen:
+ p=read(dlen)
+ s=h[19:]
+ else:
+ return self.store(oid, '', '', version, data, serial)
self.store(oid, p, s, version, data, serial)
else:
@@ -296,7 +308,7 @@
if len(h)==27 and h[8] in 'nv' and h[:8]==oid:
tlen, vlen, dlen = unpack(">iHi", h[9:19])
else: tlen=-1
- if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen:
+ if tlen <= 0 or vlen < 0 or dlen < 0 or vlen+dlen > tlen:
del self._index[oid]
return None
@@ -318,6 +330,9 @@
def store(self, oid, p, s, version, pv, sv):
+ if not s:
+ p=''
+ s='\0\0\0\0\0\0\0\0'
tlen=31+len(p)
if version:
tlen=tlen+len(version)+12+len(pv)
@@ -332,11 +347,14 @@
stlen=pack(">I",tlen)
write=f.write
write(oid+'v'+stlen+pack(">HI", vlen, len(p))+s)
- write(p)
+ if p: write(p)
if version:
+ write(version)
write(pack(">I", len(pv)))
write(pv)
- write(sv+stlen)
+ write(sv)
+
+ write(stlen)
if current: self._index[oid]=-pos
else: self._index[oid]=pos
@@ -357,7 +375,7 @@
if len(h)==27 and h[8] in 'vni':
tlen, vlen, dlen = unpack(">iHi", h[9:19])
else: tlen=-1
- if tlen <= 0 or vlen < 0 or dlen <= 0 or vlen+dlen > tlen:
+ if tlen <= 0 or vlen < 0 or dlen < 0 or vlen+dlen > tlen:
break
oid=h[:8]
@@ -379,7 +397,7 @@
serial[oid]=h[-8:], vs
else:
if serial.has_key(oid):
- # We has a record for this oid, but it was invalidated!
+ # We have a record for this oid, but it was invalidated!
del serial[oid]
del index[oid]
=== Packages/ZEO/ClientStorage.py 1.11 => 1.11.2.1 ===
self._connected=0
thread.start_new_thread(self._call.connect,(0,))
+ try: self._commit_lock_release()
+ except: pass
+
def becomeAsync(self, map):
self._lock_acquire()
@@ -229,7 +232,11 @@
if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction)
self._lock_acquire()
- try: return self._call('abortVersion', src, self._serial)
+ try:
+ oids=self._call('abortVersion', src, self._serial)
+ invalidate=self._cache.invalidate
+ for oid in oids: invalidate(oid, src)
+ return oids
finally: self._lock_release()
def close(self):
@@ -241,7 +248,17 @@
if transaction is not self._transaction:
raise POSException.StorageTransactionError(self, transaction)
self._lock_acquire()
- try: return self._call('commitVersion', src, dest, self._serial)
+ try:
+ oids=self._call('commitVersion', src, dest, self._serial)
+ invalidate=self._cache.invalidate
+ if dest:
+ # just invalidate our version data
+ for oid in oids: invalidate(oid, src)
+ else:
+ # dest is '', so invalidate version and non-version
+ for oid in oids: invalidate(oid, dest)
+
+ return oids
finally: self._lock_release()
def getName(self):
@@ -265,11 +282,12 @@
self._lock_acquire()
try:
p = self._cache.load(oid, version)
- if p is not None: return p
+ if p: return p
p, s, v, pv, sv = self._call('zeoLoad', oid)
self._cache.store(oid, p, s, v, pv, sv)
if not v or not version or version != v:
- return p, s
+ if s: return p, s
+ raise KeyError, oid # no non-version data for this
return pv, sv
finally: self._lock_release()
@@ -465,5 +483,5 @@
def versions(self, max=None):
self._lock_acquire()
- try: return self._call('versionEmpty', max)
+ try: return self._call('versions', max)
finally: self._lock_release()
=== Packages/ZEO/StorageServer.py 1.11 => 1.11.2.1 ===
return storage, storage_id
+ def register_storage(self, connection, storage, storage_method):
+ i=id(storage)
+ connections=self.__get_connections(i, None)
+ if connections is None: self.__connections[i]=[connection]
+ else: connections.append(connection)
+ connection.setStorage(i, storage, storage_method)
+
+ def register_connection(self, connection, storage_id):
+ validator=self.__storages.get(storage_id, None)
+ if validator is None:
+ LOG('ZEO Server', PROBLEM, 'Invalid storage, %s' % storage_id)
+ connection.close()
+ return
+
+ try:
+ validator.validate(storage_id, self, connection)
+ except:
+ if hasattr(validator, 'tpc_begin'):
+ # This is a storage, not a validator. Just register
+ # it.
+ self.register_storage(connection, validator, storage_method)
+ return
+ LOG('ZEO Server', ERROR, 'Invalid validator for %s' % storage_id)
+ connection.close()
+ return
+
def unregister_connection(self, connection, storage_id):
connections=self.__get_connections(storage_id, None)
@@ -185,16 +211,23 @@
log=log_info
+read_storage_methods={}
+for n in (
+ 'get_info', 'history', 'load', 'loadSerial', 'modifiedInVersion',
+ 'tpc_abort', 'tpc_begin', 'tpc_begin_sync', 'tpc_finish',
+ 'undoLog', 'undoInfo', 'versionEmpty', 'versions', 'vote',
+ 'zeoLoad', 'zeoVerify', 'beginZeoVerify', 'endZeoVerify',
+ ):
+ read_storage_methods[n]=1
+read_storage_method=read_storage_methods.has_key
+
storage_methods={}
for n in (
- 'get_info', 'abortVersion', 'commitVersion',
- 'history', 'load', 'loadSerial',
- 'modifiedInVersion', 'new_oid', 'new_oids', 'pack', 'store',
- 'storea', 'tpc_abort', 'tpc_begin', 'tpc_begin_sync',
- 'tpc_finish', 'undo', 'undoLog', 'undoInfo', 'versionEmpty',
- 'vote', 'zeoLoad', 'zeoVerify', 'beginZeoVerify', 'endZeoVerify',
+ 'abortVersion', 'commitVersion', 'new_oid', 'new_oids', 'pack',
+ 'store', 'storea', 'undo',
):
storage_methods[n]=1
+storage_methods.update(read_storage_methods)
storage_method=storage_methods.has_key
def find_global(module, name,
@@ -238,6 +271,10 @@
self.__closed=1
SizedMessageAsyncConnection.close(self)
+ def setStorage(i, s, m):
+ self.__storage, self.__storage_id = s, i
+ self.storage_method=m
+
def message_input(self, message,
dump=dump, Unpickler=Unpickler, StringIO=StringIO,
None=None):
@@ -247,8 +284,7 @@
blather('message_input', m)
if self.__storage is None:
- self.__storage, self.__storage_id = (
- self.__server.register_connection(self, message))
+ self.__server.register_connection(self, message)
return
rt='R'
@@ -262,10 +298,10 @@
name, args = args[0], args[1:]
if __debug__:
m=`tuple(args)`
- if len(m) > 60: m=m[:60]+' ...'
+ if len(m) > 90: m=m[:90]+' ...'
blather('call: %s%s' % (name, m))
- if not storage_method(name):
+ if not self.storage_method(name):
raise 'Invalid Method Name', name
if hasattr(self, name):
r=apply(getattr(self, name), args)
@@ -283,7 +319,14 @@
if len(m) > 60: m=m[:60]+' ...'
blather('%s: %s' % (rt, m))
- r=dump(r,1)
+ try: r=dump(r,1)
+ except:
+ # Ugh, must be an unpicklable exception
+ r=StorageServerError("Couldn't pickle result %s" % `r`)
+ dump('',1) # clear pickler
+ r=dump(r,1)
+ rt='E'
+
self.message_output(rt+r)
def get_info(self):
@@ -301,8 +344,16 @@
v=storage.modifiedInVersion(oid)
if v: pv, sv = storage.load(oid, v)
else: pv=sv=None
- p, s = storage.load(oid,'')
+ try:
+ p, s = storage.load(oid,'')
+ except KeyError:
+ if sv:
+ # Created in version, no non-version data
+ p=s=None
+ else:
+ raise
return p, s, v, pv, sv
+
def beginZeoVerify(self):
self.message_output('bN.')
@@ -337,6 +388,26 @@
def _pack(self, t):
self.__storage.pack(t, referencesf)
self.message_output('S'+dump(self.get_info(), 1))
+
+ def abortVersion(self, src, id):
+ t=self._transaction
+ if t is None or id != t.id:
+ raise POSException.StorageTransactionError(self, id)
+ oids=self.__storage.abortVersion(src, t)
+ a=self.__invalidated.append
+ for oid in oids: a((oid,src))
+ return oids
+
+ def commitVersion(self, src, dest, id):
+ t=self._transaction
+ if t is None or id != t.id:
+ raise POSException.StorageTransactionError(self, id)
+ oids=self.__storage.commitVersion(src, dest, t)
+ a=self.__invalidated.append
+ for oid in oids:
+ a((oid,dest))
+ if dest: a((oid,src))
+ return oids
def store(self, oid, serial, data, version, id):
t=self._transaction