Commit b398e744 authored by Jim Fulton's avatar Jim Fulton Committed by GitHub

Merge pull request #53 from zopefoundation/client-credentials

Added the ability to pass credentials when creating client storages.
parents 02943acd 9fda3635
Changelog
=========
- Added the ability to pass credentials when creating client storages.
This is experimental in that passing credentials will cause
connections to an ordinary ZEO server to fail, but it facilitates
experimentation with custom ZEO servers. Doing this with custom ZEO
clients would have been awkward due to the many levels of
composition involved.
In the future, we expect to support server security plugins that
consume credentials for authentication (typically over SSL).
Note that credentials are opaque to ZEO. They can be any object with
a true value. The client mearly passes them to the server, which
will someday pass them to a plugin.
5.0.0a1 (2016-07-21)
--------------------
......
......@@ -99,6 +99,7 @@ class ClientStorage(ZODB.ConflictResolution.ConflictResolvingStorage):
wait=True,
drop_cache_rather_verify=True,
username=None, password=None, realm=None,
credentials=None,
# For tests:
_client_factory=ZEO.asyncio.client.ClientThread,
):
......@@ -261,6 +262,7 @@ class ClientStorage(ZODB.ConflictResolution.ConflictResolvingStorage):
ZEO.asyncio.client.Fallback if read_only_fallback else read_only,
wait_timeout or 30,
ssl = ssl, ssl_server_hostname=ssl_server_hostname,
credentials=credentials,
)
self._call = self._server.call
self._async = self._server.async
......
......@@ -73,7 +73,8 @@ class Protocol(base.Protocol):
def __init__(self, loop,
addr, client, storage_key, read_only, connect_poll=1,
heartbeat_interval=60, ssl=None, ssl_server_hostname=None):
heartbeat_interval=60, ssl=None, ssl_server_hostname=None,
credentials=None):
"""Create a client interface
addr is either a host,port tuple or a string file name.
......@@ -93,6 +94,7 @@ class Protocol(base.Protocol):
self.futures = {} # { message_id -> future }
self.ssl = ssl
self.ssl_server_hostname = ssl_server_hostname
self.credentials = credentials
self.connect()
......@@ -178,17 +180,19 @@ class Protocol(base.Protocol):
self._write(self.protocol_version)
credentials = (self.credentials,) if self.credentials else ()
try:
try:
server_tid = yield self.fut(
'register', self.storage_key,
self.read_only if self.read_only is not Fallback else False,
)
*credentials)
except ZODB.POSException.ReadOnlyError:
if self.read_only is Fallback:
self.read_only = True
server_tid = yield self.fut(
'register', self.storage_key, True)
'register', self.storage_key, True, *credentials)
else:
raise
else:
......@@ -284,7 +288,7 @@ class Client(object):
def __init__(self, loop,
addrs, client, cache, storage_key, read_only, connect_poll,
register_failed_poll=9,
ssl=None, ssl_server_hostname=None):
ssl=None, ssl_server_hostname=None, credentials=None):
"""Create a client interface
addr is either a host,port tuple or a string file name.
......@@ -302,6 +306,7 @@ class Client(object):
self.client = client
self.ssl = ssl
self.ssl_server_hostname = ssl_server_hostname
self.credentials = credentials
for name in Protocol.client_delegated:
setattr(self, name, getattr(client, name))
self.cache = cache
......@@ -367,6 +372,7 @@ class Client(object):
self.storage_key, self.read_only, self.connect_poll,
ssl=self.ssl,
ssl_server_hostname=self.ssl_server_hostname,
credentials=self.credentials,
)
for addr in self.addrs
]
......@@ -730,10 +736,12 @@ class ClientThread(ClientRunner):
def __init__(self, addrs, client, cache,
storage_key='1', read_only=False, timeout=30,
disconnect_poll=1, ssl=None, ssl_server_hostname=None):
disconnect_poll=1, ssl=None, ssl_server_hostname=None,
credentials=None):
self.set_options(addrs, client, cache, storage_key, read_only,
timeout, disconnect_poll,
ssl=ssl, ssl_server_hostname=ssl_server_hostname)
ssl=ssl, ssl_server_hostname=ssl_server_hostname,
credentials=credentials)
self.thread = threading.Thread(
target=self.run,
name="%s zeo client networking thread" % client.__name__,
......
"""Clients can pass credentials to a server.
This is an experimental feature to enable server authentication and
authorization.
"""
from zope.testing import setupstack
import unittest
import ZEO.StorageServer
class ClientAuthTests(setupstack.TestCase):
def setUp(self):
self.setUpDirectory()
self.__register = ZEO.StorageServer.ZEOStorage.register
def tearDown(self):
ZEO.StorageServer.ZEOStorage.register = self.__register
def test_passing_credentials(self):
# First, we'll temporarily swap the storage server register
# method with one that let's is see credentials that were passed:
creds_log = []
def register(zs, storage_id, read_only, credentials=self):
creds_log.append(credentials)
return self.__register(zs, storage_id, read_only)
ZEO.StorageServer.ZEOStorage.register = register
# Now start an in process server
addr, stop = ZEO.server()
# If we connect, without providing credentials, then no
# credentials will be passed to register:
client = ZEO.client(addr)
self.assertEqual(creds_log, [self])
client.close()
creds_log.pop()
# But if we pass credentials, they'll be passed to register:
creds = dict(user='me', password='123')
client = ZEO.client(addr, credentials=creds)
self.assertEqual(creds_log, [creds])
client.close()
stop()
def test_suite():
return unittest.makeSuite(ClientAuthTests)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment