summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2014-04-29 16:03:12 +0000
committerGerrit Code Review <review@openstack.org>2014-04-29 16:03:12 +0000
commit21243f5dc9d8d29c26f06db358c782364af95a06 (patch)
tree9afc9c499686401d8ea32ca7954a139e43aabc41
parent5e5351edd3c923c42e22efa01f4df2d1a26c3256 (diff)
parent27e5d6366128cecb97e8c88a0fac89eb47b35eb8 (diff)
downloadgear-21243f5dc9d8d29c26f06db358c782364af95a06.tar.gz
Merge "Add access control"
-rw-r--r--doc/source/index.rst86
-rw-r--r--gear/__init__.py171
-rw-r--r--gear/acl.py289
-rw-r--r--gear/cmd/geard.py26
-rw-r--r--gear/tests/test_gear.py56
5 files changed, 620 insertions, 8 deletions
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 5b62114..c8fa5ec 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -158,6 +158,25 @@ Server Usage
------------
.. program-output:: geard --help
+The syntax of the optional ACL file consists of a number of sections
+identified by the SSL certificate Common Name Subject, and the
+arguments to the :py:class:`ACLEntry` constructor as key-value pairs::
+
+ [<subject>]
+ register=<regex>
+ invoke=<regex>
+ grant=<boolean>
+
+For example::
+
+ [my_worker]
+ register=.*
+
+ [my_client]
+ invoke=.*
+
+ [my_node_manager]
+ grant=True
Server Objects
^^^^^^^^^^^^^^
@@ -166,6 +185,73 @@ Server Objects
:inherited-members:
+Access Control
+--------------
+
+The gear server supports authorization via access control lists. When
+an :py:class:`ACL` object is supplied to the server (or a file on the
+command line), gear changes from the normal Gearman mode of
+allow-by-default to deny-by-default and only clients with ACL entries
+will be able to perform actions such as registering functions or
+submitting jobs. Authorization is based on the SSL certificate Common
+Name Subject associated with the connection. An :py:class:`ACL`
+object may be modified programatically at run-time.
+
+The administrative protocol supports modifying ACLs with the following
+commands:
+
+**acl list**
+ List the current acls::
+
+ acl list
+ client register=None invoke=.* grant=True
+ worker register=.* invoke=None grant=True
+ .
+
+**acl grant <verb> <subject> <pattern>**
+ Grant the `<verb>` action for functions matching `<pattern>` to
+ `<subject>`. Verbs can be one of ``register``, ``invoke``, or
+ ``grant``. This requires the current connection have the grant
+ permission. Example::
+
+ acl grant register worker .*
+ OK
+
+**acl revoke <verb> <subject>**
+ Revoke the `<verb>` action from `<subject>`. Verbs can be one of
+ ``register``, ``invoke``, ``grant``, or ``all`` to indicate all
+ permissions for the subject should be revoked. This requires the
+ grant permission, except that a subject may always revoke its own
+ permissions. Example::
+
+ acl revoke register worker
+ OK
+
+**acl self-revoke <verb>**
+ Revoke the `<verb>` action from connections associted with the
+ current certificate subject. Verbs can be one of ``register``,
+ ``invoke``, ``grant``, or ``all`` to indicate all permissions for
+ the subject should be revoked. This is similar to ``acl revoke``
+ but is a convenience method so that a subject does not need to know
+ its own common name. A subject always has permission to revoke its
+ own permissions. Example::
+
+ acl self-revoke register
+ OK
+
+ACL Objects
+^^^^^^^^^^^
+.. autoclass:: gear.ACL
+ :members:
+ :inherited-members:
+
+ACLEntry Objects
+^^^^^^^^^^^^^^^^
+.. autoclass:: gear.ACLEntry
+ :members:
+ :inherited-members:
+
+
Common
------
diff --git a/gear/__init__.py b/gear/__init__.py
index e26d698..dcdb15b 100644
--- a/gear/__init__.py
+++ b/gear/__init__.py
@@ -1,4 +1,4 @@
-# Copyright 2013 OpenStack Foundation
+# Copyright 2013-2014 OpenStack Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
@@ -23,6 +23,7 @@ import time
import uuid as uuid_module
from gear import constants
+from gear.acl import ACLError, ACLEntry, ACL # noqa
try:
import Queue as queue
@@ -1292,10 +1293,14 @@ class Client(BaseClient):
else:
raise ConfigurationError("Invalid precedence value")
packet = Packet(constants.REQ, cmd, data)
+ attempted_connections = set()
while True:
+ if attempted_connections == set(self.active_connections):
+ break
conn = self.getConnection()
task = SubmitJobTask(job)
conn.pending_tasks.append(task)
+ attempted_connections.add(conn)
try:
self.sendPacket(packet, conn)
except Exception:
@@ -2132,6 +2137,12 @@ class ServerConnection(Connection):
self.client_id = None
self.functions = set()
self.related_jobs = {}
+ self.ssl_subject = None
+ if self.use_ssl:
+ for x in conn.getpeercert()['subject']:
+ if x[0][0] == 'commonName':
+ self.ssl_subject = x[0][1]
+ self.log.debug("SSL subject: %s" % self.ssl_subject)
self.changeState("INIT")
def _getAdminRequest(self):
@@ -2161,11 +2172,13 @@ class Server(BaseClientServer):
:arg str client_id: The ID associated with this server.
It will be appending to the name of the logger (e.g.,
gear.Server.server_id). Defaults to 'unknown'.
+ :arg ACL acl: An :py:class:`ACL` object if the server should apply
+ access control rules to its connections.
"""
def __init__(self, port=4730, ssl_key=None, ssl_cert=None, ssl_ca=None,
statsd_host=None, statsd_port=8125, statsd_prefix=None,
- server_id='unknown'):
+ server_id='unknown', acl=None):
self.port = port
self.ssl_key = ssl_key
self.ssl_cert = ssl_cert
@@ -2176,6 +2189,7 @@ class Server(BaseClientServer):
self.jobs = {}
self.functions = set()
self.max_handle = 0
+ self.acl = acl
self.connect_wake_read, self.connect_wake_write = os.pipe()
self.use_ssl = False
@@ -2322,6 +2336,29 @@ class Server(BaseClientServer):
self.handleStatus(request)
elif request.command.startswith(b'workers'):
self.handleWorkers(request)
+ elif request.command.startswith(b'acl list'):
+ self.handleACLList(request)
+ elif request.command.startswith(b'acl grant'):
+ self.handleACLGrant(request)
+ elif request.command.startswith(b'acl revoke'):
+ self.handleACLRevoke(request)
+ elif request.command.startswith(b'acl self-revoke'):
+ self.handleACLSelfRevoke(request)
+
+ def _cancelJob(self, request, job, queue):
+ if self.acl:
+ if not self.acl.canInvoke(request.connection.ssl_subject,
+ job.name):
+ self.log.info("Rejecting cancel job from %s for %s "
+ "due to ACL" %
+ (request.connection.ssl_subject, job.name))
+ request.connection.sendRaw(b'ERR PERMISSION_DENIED\n')
+ return
+ queue.remove(job)
+ del self.jobs[job.handle]
+ self._updateStats()
+ request.connection.sendRaw(b'OK\n')
+ return
def handleCancelJob(self, request):
words = request.command.split()
@@ -2331,13 +2368,111 @@ class Server(BaseClientServer):
for queue in [self.high_queue, self.normal_queue, self.low_queue]:
for job in queue:
if handle == job.handle:
- queue.remove(job)
- del self.jobs[handle]
- self._updateStats()
- request.connection.sendRaw(b'OK\n')
- return
+ return self._cancelJob(request, job, queue)
request.connection.sendRaw(b'ERR UNKNOWN_JOB\n')
+ def handleACLList(self, request):
+ if self.acl is None:
+ request.connection.sendRaw(b'ERR ACL_DISABLED\n')
+ return
+ for entry in self.acl.getEntries():
+ l = "%s\tregister=%s\tinvoke=%s\tgrant=%s\n" % (
+ entry.subject, entry.register, entry.invoke, entry.grant)
+ request.connection.sendRaw(l.encode('utf8'))
+ request.connection.sendRaw(b'.\n')
+
+ def handleACLGrant(self, request):
+ # acl grant register worker .*
+ words = request.command.split(None, 4)
+ verb = words[2]
+ subject = words[3]
+
+ if self.acl is None:
+ request.connection.sendRaw(b'ERR ACL_DISABLED\n')
+ return
+ if not self.acl.canGrant(request.connection.ssl_subject):
+ request.connection.sendRaw(b'ERR PERMISSION_DENIED\n')
+ return
+ try:
+ if verb == 'invoke':
+ self.acl.grantInvoke(subject, words[4])
+ elif verb == 'register':
+ self.acl.grantRegister(subject, words[4])
+ elif verb == 'grant':
+ self.acl.grantGrant(subject)
+ else:
+ request.connection.sendRaw(b'ERR UNKNOWN_ACL_VERB\n')
+ return
+ except ACLError, e:
+ self.log.info("Error in grant command: %s" % (e.message,))
+ request.connection.sendRaw(b'ERR UNABLE %s\n' % (e.message,))
+ return
+ request.connection.sendRaw(b'OK\n')
+
+ def handleACLRevoke(self, request):
+ # acl revoke register worker
+ words = request.command.split()
+ verb = words[2]
+ subject = words[3]
+
+ if self.acl is None:
+ request.connection.sendRaw(b'ERR ACL_DISABLED\n')
+ return
+ if subject != request.connection.ssl_subject:
+ if not self.acl.canGrant(request.connection.ssl_subject):
+ request.connection.sendRaw(b'ERR PERMISSION_DENIED\n')
+ return
+ try:
+ if verb == 'invoke':
+ self.acl.revokeInvoke(subject)
+ elif verb == 'register':
+ self.acl.revokeRegister(subject)
+ elif verb == 'grant':
+ self.acl.revokeGrant(subject)
+ elif verb == 'all':
+ try:
+ self.acl.remove(subject)
+ except ACLError:
+ pass
+ else:
+ request.connection.sendRaw(b'ERR UNKNOWN_ACL_VERB\n')
+ return
+ except ACLError, e:
+ self.log.info("Error in revoke command: %s" % (e.message,))
+ request.connection.sendRaw(b'ERR UNABLE %s\n' % (e.message,))
+ return
+ request.connection.sendRaw(b'OK\n')
+
+ def handleACLSelfRevoke(self, request):
+ # acl self-revoke register
+ words = request.command.split()
+ verb = words[2]
+
+ if self.acl is None:
+ request.connection.sendRaw(b'ERR ACL_DISABLED\n')
+ return
+ subject = request.connection.ssl_subject
+ try:
+ if verb == 'invoke':
+ self.acl.revokeInvoke(subject)
+ elif verb == 'register':
+ self.acl.revokeRegister(subject)
+ elif verb == 'grant':
+ self.acl.revokeGrant(subject)
+ elif verb == 'all':
+ try:
+ self.acl.remove(subject)
+ except ACLError:
+ pass
+ else:
+ request.connection.sendRaw(b'ERR UNKNOWN_ACL_VERB\n')
+ return
+ except ACLError, e:
+ self.log.info("Error in self-revoke command: %s" % (e.message,))
+ request.connection.sendRaw(b'ERR UNABLE %s\n' % (e.message,))
+ return
+ request.connection.sendRaw(b'OK\n')
+
def _getFunctionStats(self):
functions = {}
for function in self.functions:
@@ -2442,6 +2577,14 @@ class Server(BaseClientServer):
if not unique:
unique = None
arguments = packet.getArgument(2, True)
+ if self.acl:
+ if not self.acl.canInvoke(packet.connection.ssl_subject, name):
+ self.log.info("Rejecting SUBMIT_JOB from %s for %s "
+ "due to ACL" %
+ (packet.connection.ssl_subject, name))
+ self.sendError(packet.connection, 0,
+ 'Permission denied by ACL')
+ return
self.max_handle += 1
handle = ('H:%s:%s' % (packet.connection.host,
self.max_handle)).encode('utf8')
@@ -2546,8 +2689,22 @@ class Server(BaseClientServer):
name = packet.getArgument(0)
packet.connection.client_id = name
+ def sendError(self, connection, code, text):
+ data = (str(code).encode('utf8') + b'\x00' +
+ str(text).encode('utf8') + b'\x00')
+ p = Packet(constants.RES, constants.ERROR, data)
+ connection.sendPacket(p)
+
def handleCanDo(self, packet):
name = packet.getArgument(0)
+ if self.acl:
+ if not self.acl.canRegister(packet.connection.ssl_subject, name):
+ self.log.info("Ignoring CAN_DO from %s for %s due to ACL" %
+ (packet.connection.ssl_subject, name))
+ # CAN_DO normally does not merit a response so it is
+ # not clear that it is appropriate to send an ERROR
+ # response at this point.
+ return
self.log.debug("Adding function %s to %s" % (name, packet.connection))
packet.connection.functions.add(name)
self.functions.add(name)
diff --git a/gear/acl.py b/gear/acl.py
new file mode 100644
index 0000000..ea79a67
--- /dev/null
+++ b/gear/acl.py
@@ -0,0 +1,289 @@
+# Copyright 2014 OpenStack Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+
+
+class ACLError(Exception):
+ pass
+
+
+class ACLEntry(object):
+ """An access control list entry.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+
+ :arg str register: A regular expression that matches the jobs that
+ connections with this certificate are permitted to register.
+
+ :arg str invoke: A regular expression that matches the jobs that
+ connections with this certificate are permitted to invoke.
+ Also implies the permission to cancel the same set of jobs in
+ the queue.
+
+ :arg boolean grant: A flag indicating whether connections with
+ this certificate are permitted to grant access to other
+ connections. Also implies the permission to revoke access
+ from other connections. The ability to self-revoke access is
+ always implied.
+ """
+
+ def __init__(self, subject, register=None, invoke=None, grant=False):
+ self.subject = subject
+ self.setRegister(register)
+ self.setInvoke(invoke)
+ self.setGrant(grant)
+
+ def __repr__(self):
+ return ('<ACLEntry for %s register=%s invoke=%s grant=%s>' %
+ (self.subject, self.register, self.invoke, self.grant))
+
+ def isEmpty(self):
+ """Checks whether this entry grants any permissions at all.
+
+ :returns: False if any permission is granted, otherwise True.
+ """
+ if (self.register is None and
+ self.invoke is None and
+ self.grant is False):
+ return True
+ return False
+
+ def canRegister(self, name):
+ """Check whether this subject is permitted to register a function.
+
+ :arg str name: The function name to check.
+ :returns: A boolean indicating whether the action should be permitted.
+ """
+ if self.register is None:
+ return False
+ if not self._register.match(name):
+ return False
+ return True
+
+ def canInvoke(self, name):
+ """Check whether this subject is permitted to register a function.
+
+ :arg str name: The function name to check.
+ :returns: A boolean indicating whether the action should be permitted.
+ """
+ if self.invoke is None:
+ return False
+ if not self._invoke.match(name):
+ return False
+ return True
+
+ def setRegister(self, register):
+ """Sets the functions that this subject can register.
+
+ :arg str register: A regular expression that matches the jobs that
+ connections with this certificate are permitted to register.
+ """
+ self.register = register
+ if register:
+ try:
+ self._register = re.compile(register)
+ except re.error, e:
+ raise ACLError('Regular expression error: %s' % (e.message,))
+ else:
+ self._register = None
+
+ def setInvoke(self, invoke):
+ """Sets the functions that this subject can invoke.
+
+ :arg str invoke: A regular expression that matches the jobs that
+ connections with this certificate are permitted to invoke.
+ """
+ self.invoke = invoke
+ if invoke:
+ try:
+ self._invoke = re.compile(invoke)
+ except re.error, e:
+ raise ACLError('Regular expression error: %s' % (e.message,))
+ else:
+ self._invoke = None
+
+ def setGrant(self, grant):
+ """Sets whether this subject can grant ACLs to others.
+
+ :arg boolean grant: A flag indicating whether connections with
+ this certificate are permitted to grant access to other
+ connections. Also implies the permission to revoke access
+ from other connections. The ability to self-revoke access is
+ always implied.
+ """
+ self.grant = grant
+
+
+class ACL(object):
+ """An access control list.
+
+ ACLs are deny-by-default. The checked actions are only allowed if
+ there is an explicit rule in the ACL granting permission for a
+ given client (identified by SSL certificate Common Name Subject)
+ to perform that action.
+ """
+
+ def __init__(self):
+ self.subjects = {}
+
+ def add(self, entry):
+ """Add an ACL entry.
+
+ :arg Entry entry: The :py:class:`ACLEntry` to add.
+ :raises ACLError: If there is already an entry for the subject.
+ """
+ if entry.subject in self.subjects:
+ raise ACLError("An ACL entry for %s already exists" %
+ (entry.subject,))
+ self.subjects[entry.subject] = entry
+
+ def remove(self, subject):
+ """Remove an ACL entry.
+
+ :arg str subject: The SSL certificate Subject Common Name to
+ remove from the ACL.
+ :raises ACLError: If there is no entry for the subject.
+ """
+ if subject not in self.subjects:
+ raise ACLError("There is no ACL entry for %s" % (subject,))
+ del self.subjects[subject]
+
+ def getEntries(self):
+ """Return a list of current ACL entries.
+
+ :returns: A list of :py:class:`ACLEntry` objects.
+ """
+ items = self.subjects.items()
+ items.sort(lambda a, b: cmp(a[0], b[0]))
+ return [x[1] for x in items]
+
+ def canRegister(self, subject, name):
+ """Check whether a subject is permitted to register a function.
+
+ :arg str subject: The SSL certificate Subject Common Name to
+ check against.
+ :arg str name: The function name to check.
+ :returns: A boolean indicating whether the action should be permitted.
+ """
+ entry = self.subjects.get(subject)
+ if entry is None:
+ return False
+ return entry.canRegister(name)
+
+ def canInvoke(self, subject, name):
+ """Check whether a subject is permitted to invoke a function.
+
+ :arg str subject: The SSL certificate Subject Common Name to
+ check against.
+ :arg str name: The function name to check.
+ :returns: A boolean indicating whether the action should be permitted.
+ """
+ entry = self.subjects.get(subject)
+ if entry is None:
+ return False
+ return entry.canInvoke(name)
+
+ def canGrant(self, subject):
+ """Check whether a subject is permitted to grant access to others.
+
+ :arg str subject: The SSL certificate Subject Common Name to
+ check against.
+ :returns: A boolean indicating whether the action should be permitted.
+ """
+ entry = self.subjects.get(subject)
+ if entry is None:
+ return False
+ if not entry.grant:
+ return False
+ return True
+
+ def grantInvoke(self, subject, invoke):
+ """Grant permission to invoke certain functions.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+ :arg str invoke: A regular expression that matches the jobs
+ that connections with this certificate are permitted to
+ invoke. Also implies the permission to cancel the same
+ set of jobs in the queue.
+ """
+ e = self.subjects.get(subject)
+ if not e:
+ e = ACLEntry(subject)
+ self.add(e)
+ e.setInvoke(invoke)
+
+ def grantRegister(self, subject, register):
+ """Grant permission to register certain functions.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+ :arg str register: A regular expression that matches the jobs that
+ connections with this certificate are permitted to register.
+ """
+ e = self.subjects.get(subject)
+ if not e:
+ e = ACLEntry(subject)
+ self.add(e)
+ e.setRegister(register)
+
+ def grantGrant(self, subject):
+ """Grant permission to grant permissions to other connections.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+ """
+ e = self.subjects.get(subject)
+ if not e:
+ e = ACLEntry(subject)
+ self.add(e)
+ e.setGrant(True)
+
+ def revokeInvoke(self, subject):
+ """Revoke permission to invoke all functions.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+ """
+ e = self.subjects.get(subject)
+ if e:
+ e.setInvoke(None)
+ if e.isEmpty():
+ self.remove(subject)
+
+ def revokeRegister(self, subject):
+ """Revoke permission to register all functions.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+ """
+ e = self.subjects.get(subject)
+ if e:
+ e.setRegister(None)
+ if e.isEmpty():
+ self.remove(subject)
+
+ def revokeGrant(self, subject):
+ """Revoke permission to grant permissions to other connections.
+
+ :arg str subject: The SSL certificate Subject Common Name to which
+ the entry applies.
+ """
+ e = self.subjects.get(subject)
+ if e:
+ e.setGrant(False)
+ if e.isEmpty():
+ self.remove(subject)
diff --git a/gear/cmd/geard.py b/gear/cmd/geard.py
index fa33216..227f1cd 100644
--- a/gear/cmd/geard.py
+++ b/gear/cmd/geard.py
@@ -14,6 +14,7 @@
# under the License.
import argparse
+import ConfigParser
import daemon
import extras
import gear
@@ -56,6 +57,8 @@ support.
help='path to SSL public certificate')
parser.add_argument('--ssl-key', dest='ssl_key', metavar='PATH',
help='path to SSL private key')
+ parser.add_argument('--acl', dest='acl', metavar='PATH',
+ help='path to ACL file')
parser.add_argument('--version', dest='version', action='store_true',
help='show version')
self.args = parser.parse_args()
@@ -78,13 +81,34 @@ support.
statsd_host = os.environ.get('STATSD_HOST')
statsd_port = int(os.environ.get('STATSD_PORT', 8125))
statsd_prefix = os.environ.get('STATSD_PREFIX')
+ acl = None
+ if self.args.acl:
+ aclf = ConfigParser.RawConfigParser()
+ aclf.read(self.args.acl)
+ acl = gear.ACL()
+ for section in aclf.sections():
+ if aclf.has_option(section, 'register'):
+ register = aclf.get(section, 'register')
+ else:
+ register = None
+ if aclf.has_option(section, 'invoke'):
+ invoke = aclf.get(section, 'invoke')
+ else:
+ invoke = None
+ if aclf.has_option(section, 'grant'):
+ grant = aclf.getboolean(section, 'grant')
+ else:
+ grant = None
+ entry = gear.ACLEntry(section, register, invoke, grant)
+ acl.add(entry)
self.server = gear.Server(self.args.port,
self.args.ssl_key,
self.args.ssl_cert,
self.args.ssl_ca,
statsd_host,
statsd_port,
- statsd_prefix)
+ statsd_prefix,
+ acl=acl)
signal.pause()
diff --git a/gear/tests/test_gear.py b/gear/tests/test_gear.py
index d56d923..31e0d9d 100644
--- a/gear/tests/test_gear.py
+++ b/gear/tests/test_gear.py
@@ -69,6 +69,62 @@ class TestClient(tests.BaseTestCase):
self.assertTrue(job.known)
self.assertFalse(job.running)
+ def test_ACL(self):
+ acl = gear.ACL()
+ acl.add(gear.ACLEntry('worker', register='foo.*'))
+ acl.add(gear.ACLEntry('client', invoke='foo.*'))
+ acl.add(gear.ACLEntry('manager', grant=True))
+ self.assertEqual(len(acl.getEntries()), 3)
+
+ self.assertTrue(acl.canRegister('worker', 'foo-bar'))
+ self.assertTrue(acl.canRegister('worker', 'foo'))
+ self.assertFalse(acl.canRegister('worker', 'bar-foo'))
+ self.assertFalse(acl.canRegister('worker', 'bar'))
+ self.assertFalse(acl.canInvoke('worker', 'foo'))
+ self.assertFalse(acl.canGrant('worker'))
+
+ self.assertTrue(acl.canInvoke('client', 'foo-bar'))
+ self.assertTrue(acl.canInvoke('client', 'foo'))
+ self.assertFalse(acl.canInvoke('client', 'bar-foo'))
+ self.assertFalse(acl.canInvoke('client', 'bar'))
+ self.assertFalse(acl.canRegister('client', 'foo'))
+ self.assertFalse(acl.canGrant('client'))
+
+ self.assertFalse(acl.canInvoke('manager', 'bar'))
+ self.assertFalse(acl.canRegister('manager', 'foo'))
+ self.assertTrue(acl.canGrant('manager'))
+
+ acl.remove('worker')
+ acl.remove('client')
+ acl.remove('manager')
+
+ self.assertFalse(acl.canRegister('worker', 'foo'))
+ self.assertFalse(acl.canInvoke('client', 'foo'))
+ self.assertFalse(acl.canGrant('manager'))
+
+ self.assertEqual(len(acl.getEntries()), 0)
+
+ def test_ACL_register(self):
+ acl = gear.ACL()
+ acl.grantRegister('worker', 'bar.*')
+ self.assertTrue(acl.canRegister('worker', 'bar'))
+ acl.revokeRegister('worker')
+ self.assertFalse(acl.canRegister('worker', 'bar'))
+
+ def test_ACL_invoke(self):
+ acl = gear.ACL()
+ acl.grantInvoke('client', 'bar.*')
+ self.assertTrue(acl.canInvoke('client', 'bar'))
+ acl.revokeInvoke('client')
+ self.assertFalse(acl.canInvoke('client', 'bar'))
+
+ def test_ACL_grant(self):
+ acl = gear.ACL()
+ acl.grantGrant('manager')
+ self.assertTrue(acl.canGrant('manager'))
+ acl.revokeGrant('manager')
+ self.assertFalse(acl.canGrant('manager'))
+
def load_tests(loader, in_tests, pattern):
return testscenarios.load_tests_apply_scenarios(loader, in_tests, pattern)