summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJason Madden <jason+github@nextthought.com>2017-09-15 08:45:18 -0500
committerGitHub <noreply@github.com>2017-09-15 08:45:18 -0500
commitf7f3a1dfb1063933d4cfa36a68c3f9c904487cba (patch)
tree7fdfb4c6f0fbd0fc6a9435f3b4c0ab34ca421ee6
parentc192803b8e92255aea5c45349fdbd478b173224f (diff)
parente42da5125f7537b0ae7445672b0915d7f388837f (diff)
downloadzope-security-f7f3a1dfb1063933d4cfa36a68c3f9c904487cba.tar.gz
Merge pull request #43 from zopefoundation/docs
General documentation cleanup/update, and document issubclass issue
-rw-r--r--MANIFEST.in8
-rw-r--r--docs/api/adapter.rst7
-rw-r--r--docs/api/checker.rst68
-rw-r--r--docs/api/decorator.rst52
-rw-r--r--docs/api/interfaces.rst99
-rw-r--r--docs/api/management.rst7
-rw-r--r--docs/api/metaconfigure.rst5
-rw-r--r--docs/api/permission.rst11
-rw-r--r--docs/api/protectclass.rst7
-rw-r--r--docs/api/proxy.rst21
-rw-r--r--docs/api/simplepolicies.rst7
-rw-r--r--docs/api/testing.rst7
-rw-r--r--docs/api/zcml.rst93
-rw-r--r--docs/conf.py1
-rw-r--r--docs/example.rst227
-rw-r--r--docs/hacking.rst126
-rw-r--r--docs/index.rst18
-rw-r--r--docs/narr.rst543
-rw-r--r--docs/proxy.rst403
-rw-r--r--src/zope/security/adapter.py53
-rw-r--r--src/zope/security/checker.py131
-rw-r--r--src/zope/security/interfaces.py302
-rw-r--r--src/zope/security/management.py32
-rw-r--r--src/zope/security/metadirectives.py2
-rw-r--r--src/zope/security/permission.py30
-rw-r--r--src/zope/security/proxy.py17
-rw-r--r--src/zope/security/simplepolicies.py11
-rw-r--r--src/zope/security/testing.py25
-rw-r--r--src/zope/security/zcml.py8
29 files changed, 1353 insertions, 968 deletions
diff --git a/MANIFEST.in b/MANIFEST.in
index af61fab..1c748dc 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -1,5 +1,11 @@
include *.rst
include *.txt
+include *.py
+include .coveragerc
+include appveyor.yml
+include .travis.yml
+include tox.ini
+include buildout.cfg
recursive-include docs *
recursive-include src *
@@ -8,3 +14,5 @@ global-exclude *.dll
global-exclude *.pyc
global-exclude *.pyo
global-exclude *.so
+
+prune docs/_build
diff --git a/docs/api/adapter.rst b/docs/api/adapter.rst
index bf651f2..d290b47 100644
--- a/docs/api/adapter.rst
+++ b/docs/api/adapter.rst
@@ -1,6 +1,5 @@
-:mod:`zope.security.adapter`
-============================
+=======================
+ zope.security.adapter
+=======================
.. automodule:: zope.security.adapter
- :members:
- :member-order: bysource
diff --git a/docs/api/checker.rst b/docs/api/checker.rst
index 419fe5e..7c62d5c 100644
--- a/docs/api/checker.rst
+++ b/docs/api/checker.rst
@@ -1,17 +1,25 @@
-:mod:`zope.security.checker`
-============================
+=======================
+ zope.security.checker
+=======================
+
+.. currentmodule:: zope.security.checker
+
+
+Module API Documentation
+========================
.. automodule:: zope.security.checker
- :members:
- :member-order: bysource
+API Doctests
+============
+
Protections for Modules
-----------------------
-The :func:`zope.secuirty.checker.moduleChecker` API can be used to
-determine whether a module has been protected: Initially, there's no checker
-defined for the module:
+The :func:`moduleChecker` API can be used to determine whether a
+module has been protected: Initially, there's no checker defined for
+the module:
.. doctest::
@@ -20,7 +28,9 @@ defined for the module:
>>> moduleChecker(test_zcml_functest) is None
True
-We can add a checker using :func:`zope.security.metaconfigure.protectModule`:
+We can add a checker using
+:func:`zope.security.metaconfigure.protectModule` (although this is
+more commonly done using ZCML):
.. doctest::
@@ -202,26 +212,26 @@ Protections for standard objects
... return 'ForbiddenAttribute: %s' % e.args[0]
Rocks
-#####
+~~~~~
-Rocks are immuatle, non-callable objects without interesting methods. They
+Rocks are immutable, non-callable objects without interesting methods. They
*don't* get proxied.
.. doctest::
- >>> type(ProxyFactory( object() )) is object
+ >>> type(ProxyFactory(object())) is object
True
- >>> type(ProxyFactory( 1 )) is int
+ >>> type(ProxyFactory(1)) is int
True
- >>> type(ProxyFactory( 1.0 )) is float
+ >>> type(ProxyFactory(1.0)) is float
True
- >>> type(ProxyFactory( 1j )) is complex
+ >>> type(ProxyFactory(1j)) is complex
True
- >>> type(ProxyFactory( None )) is type(None)
+ >>> type(ProxyFactory(None)) is type(None)
True
- >>> type(ProxyFactory( 'xxx' )) is str
+ >>> type(ProxyFactory('xxx')) is str
True
- >>> type(ProxyFactory( True )) is type(True)
+ >>> type(ProxyFactory(True)) is type(True)
True
Datetime-reltatd instances are rocks, too:
@@ -249,7 +259,7 @@ Datetime-reltatd instances are rocks, too:
dicts
-#####
+~~~~~
We can do everything we expect to be able to do with proxied dicts.
@@ -301,7 +311,7 @@ not checking that under python > 2):
True
lists
-#####
+~~~~~
We can do everything we expect to be able to do with proxied lists.
@@ -357,7 +367,7 @@ Always available:
True
tuples
-######
+~~~~~~
We can do everything we expect to be able to do with proxied tuples.
@@ -404,7 +414,7 @@ Always available:
True
sets
-####
+~~~~
we can do everything we expect to be able to do with proxied sets.
@@ -576,7 +586,7 @@ with any kind of proxy.
frozensets
-##########
+~~~~~~~~~~
we can do everything we expect to be able to do with proxied frozensets.
@@ -754,7 +764,7 @@ with any kind of proxy.
True
iterators
-#########
+~~~~~~~~~
.. doctest::
@@ -823,7 +833,7 @@ We shouldn't be able to iterate if we don't have an assertion:
New-style classes
-#################
+~~~~~~~~~~~~~~~~~
.. doctest::
@@ -859,7 +869,7 @@ Always available:
True
New-style Instances
-###################
+~~~~~~~~~~~~~~~~~~~
.. doctest::
@@ -891,7 +901,7 @@ Always available:
Classic Classes
-###############
+~~~~~~~~~~~~~~~
.. doctest::
@@ -925,7 +935,7 @@ Always available:
True
Classic Instances
-#################
+~~~~~~~~~~~~~~~~~
.. doctest::
@@ -955,7 +965,7 @@ Always available:
True
Interfaces and declarations
-###########################
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
We can still use interfaces though proxies:
@@ -989,7 +999,7 @@ We can still use interfaces though proxies:
abstract Base Classes
-#####################
+~~~~~~~~~~~~~~~~~~~~~
We work with the ABCMeta meta class:
diff --git a/docs/api/decorator.rst b/docs/api/decorator.rst
index 59a02bd..bc38e65 100644
--- a/docs/api/decorator.rst
+++ b/docs/api/decorator.rst
@@ -1,14 +1,19 @@
-:mod:`zope.security.decorator`
-===============================
+=========================
+ zope.security.decorator
+=========================
+
+.. automodule:: zope.security.decorator
+
+API Examples
+============
+
+.. currentmodule:: zope.security.decorator
.. testsetup::
from zope.component.testing import setUp
setUp()
-.. autoclass:: zope.security.decorator.DecoratedSecurityCheckerDescriptor
- :members:
- :member-order: bysource
To illustrate, we'll create a class that will be proxied:
@@ -27,7 +32,7 @@ and a class to proxy it that uses a decorated security checker:
... b = 'b'
... __Security_checker__ = DecoratedSecurityCheckerDescriptor()
-Next we'll create and register a checker for `Foo`:
+Next we'll create and register a checker for ``Foo``:
.. doctest::
@@ -35,15 +40,15 @@ Next we'll create and register a checker for `Foo`:
>>> fooChecker = NamesChecker(['a'])
>>> defineChecker(Foo, fooChecker)
-along with a checker for `Wrapper`:
+along with a checker for ``Wrapper``:
.. doctest::
>>> wrapperChecker = NamesChecker(['b'])
>>> defineChecker(Wrapper, wrapperChecker)
-Using `selectChecker()`, we can confirm that a `Foo` object uses
- `fooChecker`:
+Using :func:`zope.security.checker.selectChecker`, we can confirm that
+ a ``Foo`` object uses ``fooChecker``:
.. doctest::
@@ -59,7 +64,7 @@ Using `selectChecker()`, we can confirm that a `Foo` object uses
... e
ForbiddenAttribute('b', <...Foo object ...>)
-and that a `Wrapper` object uses `wrappeChecker`:
+and that a ``Wrapper`` object uses ``wrappeChecker``:
.. doctest::
@@ -91,7 +96,7 @@ because both objects have checkers, we get a combined checker:
>>> checker.check(wrapper, 'b')
The decorator checker will work even with security proxied objects. To
-illustrate, we'll proxify `foo`:
+illustrate, we'll proxify ``foo``:
.. doctest::
@@ -105,7 +110,7 @@ illustrate, we'll proxify `foo`:
... e
ForbiddenAttribute('b', <...Foo object ...>)
-when we wrap the secured `foo`:
+when we wrap the secured ``foo``:
.. doctest::
@@ -123,9 +128,9 @@ we still get a combined checker:
The decorator checker has three other scenarios:
- - the wrapper has a checker but the proxied object doesn't
- - the proxied object has a checker but the wrapper doesn't
- - neither the wrapper nor the proxied object have checkers
+- the wrapper has a checker but the proxied object doesn't
+- the proxied object has a checker but the wrapper doesn't
+- neither the wrapper nor the proxied object have checkers
When the wrapper has a checker but the proxied object doesn't:
@@ -187,25 +192,12 @@ the decorator doesn't have a checker:
...
AttributeError: 'Foo' has no attribute '__Security_checker__'
- __Security_checker__ cannot be None, otherwise Checker.proxy blows
- up:
+``__Security_checker__`` cannot be None, otherwise Checker.proxy blows
+up:
>>> checker.proxy(wrapper) is wrapper
True
-
-.. autoclass:: zope.security.decorator.SecurityCheckerDecoratorBase
- :members:
- :member-order: bysource
-
-
-.. autoclass:: zope.security.decorator.DecoratorBase
- :members:
- :member-order: bysource
-
-
-
-
.. testcleanup::
from zope.component.testing import tearDown
diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst
index cf4e757..5a3bc01 100644
--- a/docs/api/interfaces.rst
+++ b/docs/api/interfaces.rst
@@ -1,98 +1,5 @@
-:mod:`zope.security.interfaces`
-===============================
+==========================
+ zope.security.interfaces
+==========================
.. automodule:: zope.security.interfaces
-
-Exceptions
-##########
-
- .. autointerface:: IUnauthorized
- :members:
- :member-order: bysource
-
- .. autoexception:: Unauthorized
-
- .. autointerface:: IForbidden
- :members:
- :member-order: bysource
-
- .. autoexception:: Forbidden
-
- .. autointerface:: IForbiddenAttribute
- :members:
- :member-order: bysource
-
- .. autoexception:: ForbiddenAttribute
-
-Utilities
-##########
-
- .. autointerface:: ISecurityManagement
- :members:
- :member-order: bysource
-
- .. autointerface:: ISecurityChecking
- :members:
- :member-order: bysource
-
- .. autointerface:: ISecurityProxyFactory
- :members:
- :member-order: bysource
-
- .. autointerface:: IChecker
- :members:
- :member-order: bysource
-
- .. autointerface:: INameBasedChecker
- :members:
- :member-order: bysource
-
- .. autointerface:: ISecurityPolicy
- :members:
- :member-order: bysource
-
-Principals
-##########
-
- .. autointerface:: IInteraction
- :members:
- :member-order: bysource
-
- .. autointerface:: IParticipation
- :members:
- :member-order: bysource
-
- .. autoexception:: NoInteraction
-
- .. autointerface:: IInteractionManagement
- :members:
- :member-order: bysource
-
- .. autointerface:: IPrincipal
- :members:
- :member-order: bysource
-
- .. autointerface:: IGroupAwarePrincipal
- :members:
- :member-order: bysource
-
- .. autointerface:: IGroupClosureAwarePrincipal
- :members:
- :member-order: bysource
-
- .. autointerface:: IGroup
- :members:
- :member-order: bysource
-
- .. autointerface:: IMemberGetterGroup
- :members:
- :member-order: bysource
-
- .. autointerface:: IMemberAwareGroup
- :members:
- :member-order: bysource
-
- .. autointerface:: IPermission
- :members:
- :member-order: bysource
-
diff --git a/docs/api/management.rst b/docs/api/management.rst
index 2c4086e..ed6d1f7 100644
--- a/docs/api/management.rst
+++ b/docs/api/management.rst
@@ -1,6 +1,5 @@
-:mod:`zope.security.management`
-===============================
+==========================
+ zope.security.management
+==========================
.. automodule:: zope.security.management
- :members:
- :member-order: bysource
diff --git a/docs/api/metaconfigure.rst b/docs/api/metaconfigure.rst
new file mode 100644
index 0000000..a4459b8
--- /dev/null
+++ b/docs/api/metaconfigure.rst
@@ -0,0 +1,5 @@
+=============================
+ zope.security.metaconfigure
+=============================
+
+.. automodule:: zope.security.metaconfigure
diff --git a/docs/api/permission.rst b/docs/api/permission.rst
index 268f8f6..107e495 100644
--- a/docs/api/permission.rst
+++ b/docs/api/permission.rst
@@ -1,5 +1,8 @@
-:mod:`zope.security.permission`
-===============================
+==========================
+ zope.security.permission
+==========================
+
+.. currentmodule:: zope.security.permission
.. testsetup::
@@ -26,7 +29,7 @@
>>> checkPermission(None, 'y')
Traceback (most recent call last):
...
- ValueError: ('Undefined permission id', 'y')
+ ValueError: ('Undefined permission ID', 'y')
The :data:`zope.security.checker.CheckerPublic` permission always exists:
@@ -105,7 +108,7 @@ The non-public permissions 'x' and 'y' are string values:
>>> print(vocab.getTermByToken('y').value)
y
-However, the public permission value is CheckerPublic:
+However, the public permission value is :data:`~.CheckerPublic`:
.. doctest::
diff --git a/docs/api/protectclass.rst b/docs/api/protectclass.rst
index fdc266b..d5cd12d 100644
--- a/docs/api/protectclass.rst
+++ b/docs/api/protectclass.rst
@@ -1,6 +1,5 @@
-:mod:`zope.security.protectclass`
-=================================
+============================
+ zope.security.protectclass
+============================
.. automodule:: zope.security.protectclass
- :members:
- :member-order: bysource
diff --git a/docs/api/proxy.rst b/docs/api/proxy.rst
index 665912a..0acb043 100644
--- a/docs/api/proxy.rst
+++ b/docs/api/proxy.rst
@@ -1,14 +1,25 @@
-:mod:`zope.security.proxy`
-===============================
+=====================
+ zope.security.proxy
+=====================
+
+.. currentmodule:: zope.security.proxy
+
+An introduction to proxies and their uses can be found in :doc:`../proxy`.
+
+.. seealso:: :ref:`proxy-known-issues`
.. testsetup::
from zope.component.testing import setUp
setUp()
-.. autofunction:: zope.security.proxy.getTestProxyItems
+.. autofunction:: getChecker
+
+.. autofunction:: removeSecurityProxy
+
+.. autofunction:: getTestProxyItems
-.. autofunction:: zope.security.proxy.isinstance
+.. autofunction:: isinstance
.. doctest::
@@ -35,7 +46,9 @@
>>> isinstance(ProxyFactory(c), C1)
True
+.. autoclass:: Proxy
+.. autoclass:: ProxyPy
.. testcleanup::
diff --git a/docs/api/simplepolicies.rst b/docs/api/simplepolicies.rst
index 8073f9d..37cdf17 100644
--- a/docs/api/simplepolicies.rst
+++ b/docs/api/simplepolicies.rst
@@ -1,6 +1,5 @@
-:mod:`zope.security.simplepolicies`
-===================================
+==============================
+ zope.security.simplepolicies
+==============================
.. automodule:: zope.security.simplepolicies
- :members:
- :member-order: bysource
diff --git a/docs/api/testing.rst b/docs/api/testing.rst
index a9c6a00..20ac9e3 100644
--- a/docs/api/testing.rst
+++ b/docs/api/testing.rst
@@ -1,6 +1,5 @@
-:mod:`zope.security.testing`
-============================
+=======================
+ zope.security.testing
+=======================
.. automodule:: zope.security.testing
- :members:
- :member-order: bysource
diff --git a/docs/api/zcml.rst b/docs/api/zcml.rst
index a23075a..fc62966 100644
--- a/docs/api/zcml.rst
+++ b/docs/api/zcml.rst
@@ -1,40 +1,16 @@
-:mod:`zope.security.zcml`
-===============================
+====================
+ zope.security.zcml
+====================
-Configuring security via ZCML
------------------------------
-
-:mod:`zope.security` provides a ZCML file that configures some utilities and
-a couple of permissions:
-
-.. doctest::
+.. currentmodule:: zope.security.zcml
- >>> from zope.component import getGlobalSiteManager
- >>> from zope.configuration.xmlconfig import XMLConfig
- >>> from zope.component.testing import setUp
- >>> import zope.security
- >>> setUp() # clear global component registry
- >>> XMLConfig('permissions.zcml', zope.security)()
+Most users will not directly need to access the contents of this
+module; they will probably just :ref:`configure via ZCML <via-zcml>`.
- >>> len(list(getGlobalSiteManager().registeredUtilities()))
- 7
+API Reference
+=============
-Clear the current state:
-
-.. doctest::
-
- >>> from zope.component.testing import setUp, tearDown
- >>> tearDown()
- >>> setUp()
-
- >>> XMLConfig('configure.zcml', zope.security)()
-
- >>> len(list(getGlobalSiteManager().registeredUtilities()))
- 10
-
-.. autoclass:: zope.security.zcml.Permission
- :members:
- :member-order: bysource
+.. autoclass:: Permission
Let's look at an example:
@@ -77,20 +53,47 @@ Now let's see whether validation works alright
>>> field._validate('zope.Public')
-.. autointerface:: zope.security.zcml.ISecurityPolicyDirective
- :members:
- :member-order: bysource
+.. autointerface:: ISecurityPolicyDirective
+
+.. autofunction:: securityPolicy
+
+.. autointerface:: IPermissionDirective
-.. autofunction:: zope.security.zcml.securityPolicy
+.. autofunction:: permission
-.. autointerface:: zope.security.zcml.IPermissionDirective
- :members:
- :member-order: bysource
+.. autointerface:: IRedefinePermission
-.. autofunction:: zope.security.zcml.permission
+.. autofunction:: redefinePermission
-.. autointerface:: zope.security.zcml.IRedefinePermission
- :members:
- :member-order: bysource
+.. _via-zcml:
-.. autofunction:: zope.security.zcml.redefinePermission
+Configuring security via ZCML
+=============================
+
+:mod:`zope.security` provides a ZCML file that configures some
+utilities and a couple of standard permissions:
+
+.. doctest::
+
+ >>> from zope.component import getGlobalSiteManager
+ >>> from zope.configuration.xmlconfig import XMLConfig
+ >>> from zope.component.testing import setUp
+ >>> import zope.security
+ >>> setUp() # clear global component registry
+ >>> XMLConfig('permissions.zcml', zope.security)()
+
+ >>> len(list(getGlobalSiteManager().registeredUtilities()))
+ 7
+
+Clear the current state:
+
+.. doctest::
+
+ >>> from zope.component.testing import setUp, tearDown
+ >>> tearDown()
+ >>> setUp()
+
+ >>> XMLConfig('configure.zcml', zope.security)()
+
+ >>> len(list(getGlobalSiteManager().registeredUtilities()))
+ 10
diff --git a/docs/conf.py b/docs/conf.py
index caabf00..31497bf 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -263,6 +263,7 @@ intersphinx_mapping = {
'https://zopeinterface.readthedocs.io/en/latest': None,
'https://zopeproxy.readthedocs.io/en/latest': None,
'https://zopeschema.readthedocs.io/en/latest': None,
+ 'https://zopelocation.readthedocs.io/en/latest': None,
}
extlinks = {'issue': ('https://github.com/zopefoundation/zope.datetime/issues/%s',
diff --git a/docs/example.rst b/docs/example.rst
new file mode 100644
index 0000000..af5f6de
--- /dev/null
+++ b/docs/example.rst
@@ -0,0 +1,227 @@
+=========
+ Example
+=========
+
+As an example we take a look at constructing a multi-agent distributed system,
+and then adding a security layer using the Zope security model onto it.
+
+Scenario
+========
+
+Our agent simulation consists of autonomous agents that live in various agent
+homes/sandboxes and perform actions that access services available at their
+current home. Agents carry around authentication tokens which signify their
+level of access within any given home. Additionally agents attempt to migrate
+from home to home randomly.
+
+The agent simulation was constructed separately from any security aspects.
+Now we want to define and integrate a security model into the simulation. The
+full code for the simulation and the security model is available separately;
+we present only relevant code snippets here for illustration as we go through
+the implementation process.
+
+For the agent simulation we want to add a security model such that we group
+agents into two authentication groups, "norse legends", including the
+principals thor, odin, and loki, and "greek men", including prometheus,
+archimedes, and thucydides.
+
+We associate permissions with access to services and homes. We differentiate
+the homes such that certain authentication groups only have access to services
+or the home itself based on the local settings of the home in which they
+reside.
+
+We define the homes/sandboxes
+
+- origin - all agents start here, and have access to all
+ services here.
+
+- valhalla - only agents in the authentication group 'norse
+ legend' can reside here.
+
+- jail - all agents can come here, but only 'norse legend's
+ can leave or access services.
+
+
+Process
+=======
+
+Loosely we define a process for implementing this security model
+
+- mapping permissions onto actions
+
+- mapping authentication tokens onto permissions
+
+- implementing checkers and security policies that use our
+ authentication tokens and permissions.
+
+- binding checkers to our simulation classes
+
+- inserting the hooks into the original simulation code to add
+ proxy wrappers to automatically check security.
+
+- inserting hooks into the original simulation to register the
+ agents as the active principal in an interaction.
+
+
+Defining a Permission Model
+===========================
+
+We define the following permissions::
+
+ NotAllowed = 'Not Allowed'
+ Public = Checker.CheckerPublic
+ TransportAgent = 'Transport Agent'
+ AccessServices = 'Access Services'
+ AccessAgents = 'Access Agents'
+ AccessTimeService = 'Access Time Services'
+ AccessAgentService = 'Access Agent Service'
+ AccessHomeService = 'Access Home Service'
+
+and create a dictionary database mapping homes to authentication groups which
+are linked to associated permissions.
+
+
+Defining and Binding Checkers
+=============================
+
+:class:`Checkers <zope.security.checker.Checker>` are the foundational
+unit for the security framework. They define what attributes can be
+accessed or set on a given instance. They can be used implicitly via
+Proxy objects, to guard all attribute access automatically or
+explicitly to check a given access for an operation.
+
+Checker construction expects two functions or dictionaries, one is
+used to map attribute names to permissions for attribute access and
+another to do the same for setting attributes.
+
+We use the following checker factory function::
+
+ def PermissionMapChecker(permissions_map={},
+ setattr_permission_func=NoSetAttr):
+ res = {}
+ for k,v in permissions_map.items():
+ for iv in v:
+ res[iv]=k
+ return checker.Checker(res.get, setattr_permission_func)
+
+ time_service_checker = PermissionMapChecker(
+ # permission : [methods]
+ {'AccessTimeService':['getTime']}
+ )
+
+with the NoSetAttr function defined as a lambda which always return the
+permission ``NotAllowed``.
+
+To bind the checkers to the simulation classes we :func:`register
+<zope.security.checker.defineChecker>` our checkers with the security
+model's global checker registry::
+
+ import sandbox_simulation
+ from zope.security.checker import defineChecker
+ defineChecker(sandbox_simulation.TimeService, time_service_checker)
+
+
+Defining a Security Policy
+==========================
+
+We implement our security policy such that it checks the current agent's
+authentication token against the given permission in the home of the object
+being accessed. (We extend a simple policy provided by the framework
+that will track participations for us)::
+
+ from zope.security.simplepolicies import ParanoidSecurityPolicy
+
+ @provider(ISecurityPolicy)
+ @implementer(IInteraction)
+ class SimulationSecurityPolicy(ParanoidSecurityPolicy):
+
+ def checkPermission(self, permission, object):
+
+ home = object.getHome()
+ db = getattr(SimulationSecurityDatabase, home.getId(), None)
+
+ if db is None:
+ return False
+
+ allowed = db.get('any', ())
+ if permission in allowed or ALL in allowed:
+ return True
+
+ if not self.participations:
+ return False
+ for participation in self.participations:
+ token = participation.principal.getAuthenticationToken()
+ allowed = db.get(token, ())
+ if permission not in allowed:
+ return False
+
+ return True
+
+Since an interaction can have more than one principal, we check that *all* of
+them are given the necessary permission. This is not really necessary since
+we only create interactions with a single active principal.
+
+There is some additional code present to allow for shortcuts in defining the
+permission database when defining permissions for all auth groups and all
+permissions.
+
+
+Integration
+===========
+
+At this point we have implemented our security model, and we need to integrate
+it with our simulation model. We do so in three separate steps.
+
+First we make it such that agents only access homes that are wrapped in a
+security proxy. By doing this all access to homes and services (proxies have
+proxied return values for their methods) is implicitly guarded by our security
+policy.
+
+The second step is that we want to associate the active agent with the
+security context so the security policy will know which agent's authentication
+token to validate against.
+
+The third step is to set our security policy as the default policy for the
+Zope security framework. It is possible to create custom security policies at
+a finer grained than global, but such is left as an exercise for the reader.
+
+
+Interaction Access
+==================
+
+The :mod:`*default* implementation <zope.security.management>` of the
+interaction management interfaces defines interactions on a per thread
+basis with a function for an accessor. This model is not appropriate
+for all systems, as it restricts one to a single active interaction
+per thread at any given moment. Reimplementing the interaction access
+methods though is easily doable and is noted here for completeness.
+
+
+Perspectives
+============
+
+It's important to keep in mind that there is a lot more that is possible using
+the security framework than what's been presented here. All of the
+interactions are interface based, such that if you need to re-implement the
+semantics to suite your application a new implementation of the interface will
+be sufficient. Additional possibilities range from restricted interpreters
+and dynamic loading of untrusted code to non Zope web application security
+systems. Insert imagination here ;-).
+
+
+Zope Perspective
+================
+
+A Zope3 programmer will never commonly need to interact with the low level
+security framework. Zope3 defines a second security package over top the low
+level framework and authentication sources and checkers are handled via zcml
+registration. Still those developing Zope3 will hopefully find this useful as
+an introduction into the underpinnings of the security framework.
+
+
+Authors
+=======
+
+- Kapil Thangavelu <hazmat at objectrealms.net>
+- Guido Wesdorp <guido at infrae.com>
+- Marius Gedminas <marius at pov.lt>
diff --git a/docs/hacking.rst b/docs/hacking.rst
index c91f54f..825eb2f 100644
--- a/docs/hacking.rst
+++ b/docs/hacking.rst
@@ -1,9 +1,10 @@
-Hacking on :mod:`zope.security`
-===============================
+=================================
+ Hacking on :mod:`zope.security`
+=================================
Getting the Code
-################
+================
The main repository for :mod:`zope.security` is in the Zope Foundation
Github repository:
@@ -35,7 +36,7 @@ You can branch the trunk from there using Bazaar:
Working in a ``virtualenv``
-###########################
+===========================
Installing
----------
@@ -54,65 +55,55 @@ environment:
.. code-block:: sh
- $ /tmp/hack-zope.security/bin/python setup.py develop
+ $ /tmp/hack-zope.security/bin/pip install -e .[test]
Running the tests
-----------------
-Then, you canrun the tests using the build-in ``setuptools`` testrunner:
+Then, you can run the tests using the zope.testrunner (or a test
+runner of your choice):
.. code-block:: sh
- $ /tmp/hack-zope.security/bin/python setup.py test
- ...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
- ----------------------------------------------------------------------
- Ran 643 tests in 0.000s
-
- OK
-
-If you have the :mod:`nose` package installed in the virtualenv, you can
-use its testrunner too:
-
-.. code-block:: sh
-
- $ /tmp/hack-zope.security/bin/easy_install nose
- ...
- $ /tmp/hack-zope.security/bin/nosetests
- ...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
- ----------------------------------------------------------------------
- Ran 655 tests in 0.000s
+ $ /tmp/hack-zope.security/bin/zope-testrunner --test-path=src
+ Running zope.testrunner.layer.UnitTests tests:
+ Set up zope.testrunner.layer.UnitTests in 0.000 seconds.
+ Running:
- OK
+ Ran 742 tests with 0 failures, 0 errors, 36 skipped in 0.253 seconds.
+ Tearing down left over layers:
+ Tear down zope.testrunner.layer.UnitTests in 0.000 seconds.
-If you have the :mod:`coverage` pacakge installed in the virtualenv,
+If you have the :mod:`coverage` package installed in the virtualenv,
you can see how well the tests cover the code:
.. code-block:: sh
- $ /tmp/hack-zope.security/bin/easy_install nose coverage
+ $ /tmp/hack-zope.security/bin/pip install coverage
+ ...
+ $ coverage run -m zope.testrunner --test-path=src
...
- $ /tmp/hack-zope.security/bin/nosetests --with coverage
- ...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
+ $ coverage report
Name Stmts Miss Cover Missing
---------------------------------------------------------------
- zope/security.py 4 0 100%
- zope/security/_compat.py 9 0 100%
- zope/security/_definitions.py 11 0 100%
- zope/security/adapter.py 45 0 100%
- zope/security/checker.py 333 0 100%
- zope/security/decorator.py 33 0 100%
- zope/security/i18n.py 4 0 100%
- zope/security/interfaces.py 65 0 100%
- zope/security/management.py 62 0 100%
- zope/security/metaconfigure.py 108 0 100%
- zope/security/metadirectives.py 38 0 100%
- zope/security/permission.py 46 0 100%
- zope/security/protectclass.py 39 0 100%
+ zope/security.py 4 0 100%
+ zope/security/_compat.py 9 0 100%
+ zope/security/_definitions.py 11 0 100%
+ zope/security/adapter.py 45 0 100%
+ zope/security/checker.py 333 0 100%
+ zope/security/decorator.py 33 0 100%
+ zope/security/i18n.py 4 0 100%
+ zope/security/interfaces.py 65 0 100%
+ zope/security/management.py 62 0 100%
+ zope/security/metaconfigure.py 108 0 100%
+ zope/security/metadirectives.py 38 0 100%
+ zope/security/permission.py 46 0 100%
+ zope/security/protectclass.py 39 0 100%
zope/security/proxy.py 164 19 88% 55, 86, 97, 119-121, 127-129, 143-144, 153-154, 158-159, 163-164, 298, 330
- zope/security/simplepolicies.py 32 0 100%
- zope/security/zcml.py 43 0 100%
+ zope/security/simplepolicies.py 32 0 100%
+ zope/security/zcml.py 43 0 100%
---------------------------------------------------------------
- TOTAL 1036 19 98%
+ TOTAL 1036 19 98%
----------------------------------------------------------------------
Ran 655 tests in 0.000s
@@ -128,7 +119,7 @@ tests, you can build the docs:
.. code-block:: sh
- $ /tmp/hack-zope.security/bin/easy_install Sphinx
+ $ /tmp/hack-zope.security/bin/pip install -e .[docs]
...
$ cd docs
$ PATH=/tmp/hack-zope.security/bin:$PATH make html
@@ -211,7 +202,7 @@ You can also test the code snippets in the documentation:
Using :mod:`zc.buildout`
-########################
+========================
Setting up the buildout
-----------------------
@@ -245,7 +236,7 @@ You can now run the tests:
Using :mod:`tox`
-################
+================
Running Tests on Multiple Python Versions
-----------------------------------------
@@ -255,38 +246,34 @@ tool designed to run tests against multiple Python versions. It creates
a ``virtualenv`` for each configured version, installs the current package
and configured dependencies into each ``virtualenv``, and then runs the
configured commands.
-
+
:mod:`zope.security` configures the following :mod:`tox` environments via
its ``tox.ini`` file:
-- The ``py26``, ``py27``, ``py33``, ``py34``, and ``pypy`` environments
+- The ``py27``, ``py34``, ``py35``, ``pypy``, etc, environments
builds a ``virtualenv`` with the appropriate interpreter,
- installs :mod:`zope.security` and dependencies, and runs the tests
- via ``python setup.py test -q``.
+ installs :mod:`zope.security` and dependencies, and runs the tests.
- The ``py27-pure`` and ``py33-pure`` environments build a ``virtualenv``
with the appropriate interpreter, installs :mod:`zope.security` and
dependencies **without compiling C extensions**, and runs the tests
via ``python setup.py test -q``.
-- The ``coverage`` environment builds a ``virtualenv`` with ``python2.6``,
- installs :mod:`zope.security`, installs
- :mod:`nose` and :mod:`coverage`, and runs ``nosetests`` with statement
- coverage.
+- The ``coverage`` environment builds a ``virtualenv``, runs all the
+ tests under :mod:`coverage`, and prints a report to stdout.
-- The ``docs`` environment builds a virtualenv with ``python2.6``, installs
- :mod:`zope.security`, installs ``Sphinx`` and
- dependencies, and then builds the docs and exercises the doctest snippets.
+- The ``docs`` environment builds a virtualenv and then builds the
+ docs and exercises the doctest snippets.
-This example requires that you have a working ``python2.6`` on your path,
+This example requires that you have a working ``python2.7`` on your path,
as well as installing ``tox``:
.. code-block:: sh
- $ tox -e py26
+ $ tox -e py27
GLOB sdist-make: .../zope.security/setup.py
- py26 sdist-reinst: .../zope.security/.tox/dist/zope.security-4.0.2dev.zip
- py26 runtests: commands[0]
+ py27 sdist-reinst: .../zope.security/.tox/dist/zope.security-4.0.2dev.zip
+ py27 runtests: commands[0]
...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
----------------------------------------------------------------------
Ran 643 tests in 0.000s
@@ -328,7 +315,7 @@ including building the docs and testing their snippets:
Contributing to :mod:`zope.security`
-####################################
+====================================
Submitting a Bug Report
-----------------------
@@ -356,14 +343,3 @@ in your fork, and push it. You can then submit a pull request from your
branch:
https://github.com/zopefoundation/zope.security/pulls
-
-If you branched the code from Launchpad using Bazaar, you have another
-option: you can "push" your branch to Launchpad:
-
-.. code-block:: sh
-
- $ bzr push lp:~jrandom/zope.security/cool_feature
-
-After pushing your branch, you can link it to a bug report on Github,
-or request that the maintainers merge your branch using the Launchpad
-"merge request" feature.
diff --git a/docs/index.rst b/docs/index.rst
index f6418b6..eb3cbf5 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -1,17 +1,20 @@
-:mod:`zope.security` Documentation
-==================================
+=============================
+ zope.security Documentation
+=============================
Narrative Documentation
------------------------
+=======================
.. toctree::
:maxdepth: 2
narr
+ example
+ proxy
hacking
API Reference
--------------
+=============
.. toctree::
:maxdepth: 2
@@ -26,13 +29,14 @@ API Reference
api/proxy
api/simplepolicies
api/testing
+ api/metaconfigure
api/zcml
-Indices and tables
-==================
+====================
+ Indices and tables
+====================
* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
-
diff --git a/docs/narr.rst b/docs/narr.rst
index 4f19fe4..d746fc3 100644
--- a/docs/narr.rst
+++ b/docs/narr.rst
@@ -1,8 +1,8 @@
-Overview
-========
+===========================
+ Overview and Introduction
+===========================
-Introduction
-------------
+.. currentmodule:: zope.security.interfaces
The Security framework provides a generic mechanism to implement security
policies on Python objects. This introduction provides a tutorial of the
@@ -10,22 +10,34 @@ framework explaining concepts, design, and going through sample usage from the
perspective of a Python programmer using the framework outside of Zope.
Definitions
------------
+===========
Principal
-~~~~~~~~~
+---------
-A generalization of a concept of a user.
+A generalization of a concept of a :class:`user <IPrincipal>`. Further
+specializations include :class:`groups of users <IGroup>` and
+principals that :class:`know what groups they belong to
+<IGroupAwarePrincipal>`. All of these principals may interact with the system.
Permission
-~~~~~~~~~~
+----------
A kind of access, i.e. permission to READ vs. permission to WRITE.
-Fundamentally the whole security framework is organized around checking
-permissions on objects.
+Fundamentally the whole security framework is organized around
+checking permissions on objects. Permissions are represented (and
+checked) as strings, with the exception of :data:`a constant
+<zope.security.checker.CheckerPublic>` that has the special meaning of
+"public", i.e., no checking needs to be done.
+
+There are :class:`permission objects <IPermission>` that can be
+registered as zope.component utilities for validation, introspection,
+and producing :func:`lists of available permissions
+<zope.security.permission.PermissionVocabulary>` to help users assign
+them to objects.
Purpose
--------
+=======
The security framework's primary purpose is to guard and check access to
Python objects. It does this by providing mechanisms for explicit and
@@ -44,10 +56,10 @@ it can be substituted with one which doesn't care about principals or
interactions at all.
Framework Components
---------------------
+====================
Low Level Components
-~~~~~~~~~~~~~~~~~~~~
+--------------------
These components provide the infrastructure for guarding attribute access and
providing hooks into the higher level security framework.
@@ -55,9 +67,10 @@ providing hooks into the higher level security framework.
Checkers
~~~~~~~~
-A checker is associated with an object kind, and provides the hooks that map
-attribute checks onto permissions deferring to the security manager (which in
-turn defers to the policy) to perform the check.
+A :class:`checker <IChecker>` is associated
+with an object kind, and provides the hooks that map attribute checks
+onto permissions deferring to the security manager (which in turn
+defers to the policy) to perform the check.
Additionally, checkers provide for creating proxies of objects associated with
the checker.
@@ -68,491 +81,41 @@ grant access based on attribute names.
Proxies
~~~~~~~
-Wrappers around Python objects that implicitly guard access to their wrapped
-contents by delegating to their associated checker. Proxies are also viral in
-nature, in that values returned by proxies are also proxied.
+:class:`Wrappers around Python objects <zope.security.proxy.Proxy>`
+that implicitly guard access to their wrapped contents by delegating
+to their associated checker. Proxies are also viral in nature, in that
+values returned by proxies are also proxied.
High Level Components
----------------------
+=====================
Security Management
-~~~~~~~~~~~~~~~~~~~
-
-Provides accessors for setting up interactions and the global security policy.
+-------------------
-Interaction
-~~~~~~~~~~~
+Provides accessors for :class:`setting up interactions
+<IInteractionManagement>` and the :class:`global security policy
+<ISecurityManagement>`.
-Stores transient information on the list of participations.
-
-Participation
-~~~~~~~~~~~~~
+:class:`Interaction <zope.security.interfaces.IInteraction>`
+------------------------------------------------------------
-Stores information about a principal participating in the interaction.
+An :class:`interaction <IInteraction>` represents zero or more
+principals manipulating or viewing (interacting with) the system.
-Security Policy
-~~~~~~~~~~~~~~~
-
-Provides a single method that accepts the object, the permission, and the
-interaction of the access being checked and is used to implement the
+Interactions also provide :func:`a single method
+<IInteraction.checkPermission>` that accepts the object and the
+permission of the access being checked and is used to implement the
application logic for the security framework.
-Narrative (agent sandbox)
--------------------------
-
-As an example we take a look at constructing a multi-agent distributed system,
-and then adding a security layer using the Zope security model onto it.
-
-Scenario
-~~~~~~~~
-
-Our agent simulation consists of autonomous agents that live in various agent
-homes/sandboxes and perform actions that access services available at their
-current home. Agents carry around authentication tokens which signify their
-level of access within any given home. Additionally agents attempt to migrate
-from home to home randomly.
-
-The agent simulation was constructed separately from any security aspects.
-Now we want to define and integrate a security model into the simulation. The
-full code for the simulation and the security model is available separately;
-we present only relevant code snippets here for illustration as we go through
-the implementation process.
-
-For the agent simulation we want to add a security model such that we group
-agents into two authentication groups, "norse legends", including the
-principals thor, odin, and loki, and "greek men", including prometheus,
-archimedes, and thucydides.
-
-We associate permissions with access to services and homes. We differentiate
-the homes such that certain authentication groups only have access to services
-or the home itself based on the local settings of the home in which they
-reside.
-
-We define the homes/sandboxes
-
- - origin - all agents start here, and have access to all
- services here.
-
- - valhalla - only agents in the authentication group 'norse
- legend' can reside here.
-
- - jail - all agents can come here, but only 'norse legend's
- can leave or access services.
-
-
-Process
-~~~~~~~
-
-Loosely we define a process for implementing this security model
-
- - mapping permissions onto actions
-
- - mapping authentication tokens onto permissions
-
- - implementing checkers and security policies that use our
- authentication tokens and permissions.
-
- - binding checkers to our simulation classes
-
- - inserting the hooks into the original simulation code to add
- proxy wrappers to automatically check security.
-
- - inserting hooks into the original simulation to register the
- agents as the active principal in an interaction.
-
-
-Defining a Permission Model
-~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We define the following permissions::
-
- NotAllowed = 'Not Allowed'
- Public = Checker.CheckerPublic
- TransportAgent = 'Transport Agent'
- AccessServices = 'Access Services'
- AccessAgents = 'Access Agents'
- AccessTimeService = 'Access Time Services'
- AccessAgentService = 'Access Agent Service'
- AccessHomeService = 'Access Home Service'
-
-and create a dictionary database mapping homes to authentication groups which
-are linked to associated permissions.
-
-
-Defining and Binding Checkers
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-Checkers are the foundational unit for the security framework. They define
-what attributes can be accessed or set on a given instance. They can be used
-implicitly via Proxy objects, to guard all attribute access automatically or
-explicitly to check a given access for an operation.
-
-Checker construction expects two functions or dictionaries, one is used to map
-attribute names to permissions for attribute access and another to do the same
-for setting attributes.
-
-We use the following checker factory function::
-
- def PermissionMapChecker(permissions_map={},
- setattr_permission_func=NoSetAttr):
- res = {}
- for k,v in permissions_map.items():
- for iv in v:
- res[iv]=k
- return checker.Checker(res.get, setattr_permission_func)
-
- time_service_checker = PermissionMapChecker(
- # permission : [methods]
- {'AccessTimeService':['getTime']}
- )
-
-with the NoSetAttr function defined as a lambda which always return the
-permission `NotAllowed`.
-
-To bind the checkers to the simulation classes we register our checkers with
-the security model's global checker registry::
-
- import sandbox_simulation
- from zope.security.checker import defineChecker
- defineChecker(sandbox_simulation.TimeService, time_service_checker)
-
-
-Defining a Security Policy
-~~~~~~~~~~~~~~~~~~~~~~~~~~
-
-We implement our security policy such that it checks the current agent's
-authentication token against the given permission in the home of the object
-being accessed::
-
- @implementer(ISecurityPolicy)
- class SimulationSecurityPolicy:
-
- createInteraction = staticmethod(simpleinteraction.createInteraction)
-
- def checkPermission(self, permission, object, interaction):
-
- home = object.getHome()
- db = getattr(SimulationSecurityDatabase, home.getId(), None)
-
- if db is None:
- return False
-
- allowed = db.get('any', ())
- if permission in allowed or ALL in allowed:
- return True
-
- if interaction is None:
- return False
- if not interaction.participations:
- return False
- for participation in interaction.participations:
- token = participation.principal.getAuthenticationToken()
- allowed = db.get(token, ())
- if permission not in allowed:
- return False
-
- return True
-
-There are no specific requirements for the interaction class, so we can just
-use `zope.security.simpleinteraction.Interaction`.
-
-Since an interaction can have more than one principal, we check that *all* of
-them are given the necessary permission. This is not really necessary since
-we only create interactions with a single active principal.
-
-There is some additional code present to allow for shortcuts in defining the
-permission database when defining permissions for all auth groups and all
-permissions.
-
-
-Integration
-~~~~~~~~~~~
-
-At this point we have implemented our security model, and we need to integrate
-it with our simulation model. We do so in three separate steps.
-
-First we make it such that agents only access homes that are wrapped in a
-security proxy. By doing this all access to homes and services (proxies have
-proxied return values for their methods) is implicitly guarded by our security
-policy.
-
-The second step is that we want to associate the active agent with the
-security context so the security policy will know which agent's authentication
-token to validate against.
-
-The third step is to set our security policy as the default policy for the
-Zope security framework. It is possible to create custom security policies at
-a finer grained than global, but such is left as an exercise for the reader.
-
-
-Interaction Access
-~~~~~~~~~~~~~~~~~~
-
-The *default* implementation of the interaction management interfaces defines
-interactions on a per thread basis with a function for an accessor. This
-model is not appropriate for all systems, as it restricts one to a single
-active interaction per thread at any given moment. Reimplementing the
-interaction access methods though is easily doable and is noted here for
-completeness.
-
-
-Perspectives
-~~~~~~~~~~~~
-
-It's important to keep in mind that there is a lot more that is possible using
-the security framework than what's been presented here. All of the
-interactions are interface based, such that if you need to re-implement the
-semantics to suite your application a new implementation of the interface will
-be sufficient. Additional possibilities range from restricted interpreters
-and dynamic loading of untrusted code to non Zope web application security
-systems. Insert imagination here ;-).
-
-
-Zope Perspective
-~~~~~~~~~~~~~~~~
-
-A Zope3 programmer will never commonly need to interact with the low level
-security framework. Zope3 defines a second security package over top the low
-level framework and authentication sources and checkers are handled via zcml
-registration. Still those developing Zope3 will hopefully find this useful as
-an introduction into the underpinnings of the security framework.
-
-
-Code
-~~~~
-
-The complete code for this example is available.
-
-- sandbox.py - the agent framework
-
-- sandbox_security.py - the security implementation and binding to the agent
- framework.
-
-
-Authors
-~~~~~~~
-
-- Kapil Thangavelu <hazmat at objectrealms.net>
-- Guido Wesdorp <guido at infrae.com>
-- Marius Gedminas <marius at pov.lt>
-
-
-Untrusted interpreters
-======================
-
-Untrusted programs are executed by untrusted interpreters. Untrusted
-interpreters make use of security proxies to prevent un-mediated
-access to assets. An untrusted interpreter defines an environment for
-running untrusted programs. All objects within the environment are
-either:
-
-- "safe" objects created internally by the environment or created in
- the course of executing the untrusted program, or
-
-- "basic" objects
-
-- security-proxied non-basic objects
-
-The environment includes proxied functions for accessing objects
-outside of the environment. These proxied functions provide the only
-way to access information outside the environment. Because these
-functions are proxied, as described below, any access to objects
-outside the environment is mediated by the target security functions.
-
-Safe objects are objects whose operations, except for attribute
-retrieval, and methods access only information stored within the
-objects or passed as arguments. Safe objects contained within the
-interpreter environment can contain only information that is already
-in the environment or computed directly from information that is
-included in the environment. For this reason, safe objects created
-within the environment cannot be used to directly access information
-outside the environment.
-
-Safe objects have some attributes that could (very) indirectly be used
-to access assets. For this reason, an untrusted interpreter always
-proxies the results of attribute accesses on a safe objects.
-
-Basic objects are safe objects that are used to represent elemental
-data values such as strings and numbers. Basic objects require a
-lower level of protection than non-basic objects, as will be described
-detail in a later section.
-
-Security proxies mediate all object operations. Any operation
-access is checked to see whether a subject is authorized to perform
-the operation. All operation results other than basic objects are, in
-turn, security proxied. Security proxies will be described in greater
-detail in a later section. Any operation on a security proxy that
-results in a non-basic object is also security proxied.
-
-All external resources needed to perform an operation are security
-proxied.
-
-Let's consider the trusted interpreter for evaluating URLs. In
-operation 1 of the example, the interpreter uses a proxied method for
-getting the system root object. Because the method is proxied, the
-result of calling the method and the operation is also proxied.
-
-The interpreter has a function for traversing objects. This function
-is proxied. When traversing an object, the function is passed an
-object and a name. In operation 2, the function is passed the result
-of operation 1, which is the proxied root object and the name 'A'. We
-may traverse an object by invoking an operation on it. For example,
-we may use an operation to get a sub-object. Because any operation on a
-proxied object returns a proxied object or a basic object, the result
-is either a proxied object or a basic object. Traversal may also look
-up a component. For example, in operation 1, we might look up a
-presentation component named "A" for the root object. In this case,
-the external object is not proxied, but, when it is returned from the
-traversal function, it is proxied (unless it is a a basic object)
-because the traversal function is proxied, and the result of calling a
-proxied function is proxied (unless the result is a basic object).
-Operation 3 proceeds in the same way.
-
-When we get to operation 4, we use a function for computing the
-default presentation of the result of operation 3. As with traversal,
-the result of getting the default presentation is either a proxied
-object or a basic object because the function for getting the default
-presentation is proxied.
-
-When we get to the last operation, we have either a proxied object or a
-basic object. If the result of operation 4 is a basic object, we
-simply convert it to a string and return it as the result page. If
-the result of operation 4 is a non-basic object, we invoke a render
-operation on it and return the result as a string.
-
-Note that an untrusted interpreter may or may not provide protection
-against excessive resource usage. Different interpreters will provide
-different levels of service with respect to limitations on resource
-usage.
-
-If an untrusted interpreter performs an attribute access, the trusted
-interpreter must proxy the result unless the result is a basic object.
-
-In summary, an untrusted interpreter assures that any access to assets
-is mediated through security proxies by creating an environment to run
-untrusted code and making sure that:
-
-- The only way to access anything from outside of the environment is
- to call functions that are proxied in the environment.
-
-- Results of any attribute access in the environment are proxied
- unless the results are basic objects.
-
-Security proxies
-----------------
-
-Security proxies are objects that wrap and mediate access to objects.
-
-The Python programming language used by Zope defines a set of specific
-named low-level operations. In addition to operations, Python objects
-can have attributes, used to represent data and methods. Attributes
-are accessed using a dot notation. Applications can, and usually do,
-define methods to provide extended object behaviors. Methods are
-accessed as attributes through the low-level operation named
-"__getattribute__". The Python code::
-
- a.b()
-
-invokes 2 operations:
-
- 1. Use the low-level `__getattribute__` operation with the name "b".
-
- 2. Use the low-level '__call__' operation on the result of the first
- operation.
-
-For all operations except the `__getattribute__` and
-`__setattribute__` operations, security proxies have a permission
-value defined by the permission-declaration subsystem. Two special
-permission values indicate that access is either forbidden (never
-allowed) or public (always allowed). For all other permission values,
-the authorization subsystem is used to decide whether the subject has
-the permission for the proxied object. If the subject has the
-permission, then access to the operation is allowed. Otherwise, access
-is denied.
-
-For getting or setting attributes, a proxy has permissions for getting
-and a permission for setting attribute values for a given attribute
-name. As described above, these permissions may be one of the two
-special permission values indicating forbidden or public access, or
-another permission value that must be checked with the authorization
-system.
-
-For all objects, Zope defines the following operations to be always public:
-
- comparison
- "__lt__", "__le__", "__eq__", "__gt__", "__ge__", "__ne__"
-
- hash
- "__hash__"
-
- boolean value
- "__nonzero__"
-
- class introspection
- "__class__"
-
- interface introspection
- "__providedBy__", "__implements__"
-
- adaptation
- "__conform__"
-
- low-level string representation
- "__repr__"
-
-The result of an operation on a proxied object is a security proxy
-unless the result is a basic value.
-
-Basic objects
+Participation
-------------
-Basic objects are safe immutable objects that contain only immutable
-subobjects. Examples of basic objects include:
-
-- Strings,
-
-- Integers (long and normal),
-
-- Floating-point objects,
-
-- Date-time objects,
-
-- Boolean objects (True and False), and
+Stores information about a single principal :class:`participating
+<zope.security.interfaces.IParticipation>` in the :class:`interaction
+<zope.security.interfaces.IInteraction>`.
-- The special (nil) object, None.
-
-Basic objects are safe, so, as described earlier, operations on basic
-objects, other than attribute access, use only information contained
-within the objects or information passed to them. For this reason,
-basic objects cannot be used to access information outside of the
-untrusted interpreter environment.
-
-The decision not to proxy basic objects is largely an optimization.
-It allows low-level safe computation to be performed without
-unnecessary overhead,
-
-Note that a basic object could contain sensitive information, but such
-a basic object would need to be obtained by making a call on a proxied
-object. Therefore, the access to the basic object in the first place
-is mediated by the security functions.
-
-Rationale for mutable safe objects
-----------------------------------
-
-Some safe objects are not basic. For these objects, we proxy the
-objects if they originate from outside of the environment. We do this
-for two reasons:
-
-1. Non-basic objects from outside the environment need to be proxied
- to prevent unauthorized access to information.
-
-2. We need to prevent un-mediated change of information from outside of
- the environment.
+Security Policy
+---------------
-We don't proxy safe objects created within the environment. This is
-safe to do because such safe objects can contain and provide access to
-information already in the environment. Sometimes the interpreter or
-the interpreted program needs to be able to create simple data
-containers to hold information computed in the course of the program
-execution. Several safe container types are provided for this
-purpose.
+A :class:`security policy <ISecurityPolicy>` is used to create the
+interaction that will ultimately be responsible for security checking.
diff --git a/docs/proxy.rst b/docs/proxy.rst
new file mode 100644
index 0000000..2b01ee0
--- /dev/null
+++ b/docs/proxy.rst
@@ -0,0 +1,403 @@
+=============================================
+ Untrusted Interpreters and Security Proxies
+=============================================
+
+Untrusted programs are executed by untrusted interpreters. Untrusted
+interpreters make use of security proxies to prevent un-mediated
+access to assets. An untrusted interpreter defines an environment for
+running untrusted programs. All objects within the environment are
+either:
+
+- "safe" objects created internally by the environment or created in
+ the course of executing the untrusted program, or
+
+- "basic" objects
+
+- security-proxied non-basic objects
+
+The environment includes proxied functions for accessing objects
+outside of the environment. These proxied functions provide the only
+way to access information outside the environment. Because these
+functions are proxied, as described below, any access to objects
+outside the environment is mediated by the target security functions.
+
+Safe objects are objects whose operations, except for attribute
+retrieval, and methods access only information stored within the
+objects or passed as arguments. Safe objects contained within the
+interpreter environment can contain only information that is already
+in the environment or computed directly from information that is
+included in the environment. For this reason, safe objects created
+within the environment cannot be used to directly access information
+outside the environment.
+
+Safe objects have some attributes that could (very) indirectly be used
+to access assets. For this reason, an untrusted interpreter always
+proxies the results of attribute accesses on a safe objects.
+
+Basic objects are safe objects that are used to represent elemental
+data values such as strings and numbers. Basic objects require a
+lower level of protection than non-basic objects, as will be described
+detail in a later section.
+
+Security proxies mediate all object operations. Any operation
+access is checked to see whether a subject is authorized to perform
+the operation. All operation results other than basic objects are, in
+turn, security proxied. Security proxies will be described in greater
+detail in a later section. Any operation on a security proxy that
+results in a non-basic object is also security proxied.
+
+All external resources needed to perform an operation are security
+proxied.
+
+Let's consider the trusted interpreter for evaluating URLs. In
+operation 1 of the example, the interpreter uses a proxied method for
+getting the system root object. Because the method is proxied, the
+result of calling the method and the operation is also proxied.
+
+The interpreter has a function for traversing objects. This function
+is proxied. When traversing an object, the function is passed an
+object and a name. In operation 2, the function is passed the result
+of operation 1, which is the proxied root object and the name 'A'. We
+may traverse an object by invoking an operation on it. For example,
+we may use an operation to get a sub-object. Because any operation on a
+proxied object returns a proxied object or a basic object, the result
+is either a proxied object or a basic object. Traversal may also look
+up a component. For example, in operation 1, we might look up a
+presentation component named "A" for the root object. In this case,
+the external object is not proxied, but, when it is returned from the
+traversal function, it is proxied (unless it is a a basic object)
+because the traversal function is proxied, and the result of calling a
+proxied function is proxied (unless the result is a basic object).
+Operation 3 proceeds in the same way.
+
+When we get to operation 4, we use a function for computing the
+default presentation of the result of operation 3. As with traversal,
+the result of getting the default presentation is either a proxied
+object or a basic object because the function for getting the default
+presentation is proxied.
+
+When we get to the last operation, we have either a proxied object or a
+basic object. If the result of operation 4 is a basic object, we
+simply convert it to a string and return it as the result page. If
+the result of operation 4 is a non-basic object, we invoke a render
+operation on it and return the result as a string.
+
+Note that an untrusted interpreter may or may not provide protection
+against excessive resource usage. Different interpreters will provide
+different levels of service with respect to limitations on resource
+usage.
+
+If an untrusted interpreter performs an attribute access, the trusted
+interpreter must proxy the result unless the result is a basic object.
+
+In summary, an untrusted interpreter assures that any access to assets
+is mediated through security proxies by creating an environment to run
+untrusted code and making sure that:
+
+- The only way to access anything from outside of the environment is
+ to call functions that are proxied in the environment.
+
+- Results of any attribute access in the environment are proxied
+ unless the results are basic objects.
+
+Security proxies
+================
+
+Security proxies are objects that wrap and mediate access to objects.
+
+The Python programming language used by Zope defines a set of specific
+named low-level operations. In addition to operations, Python objects
+can have attributes, used to represent data and methods. Attributes
+are accessed using a dot notation. Applications can, and usually do,
+define methods to provide extended object behaviors. Methods are
+accessed as attributes through the low-level operation named
+"__getattribute__". The Python code::
+
+ a.b()
+
+invokes 2 operations:
+
+ 1. Use the low-level ``__getattribute__`` operation with the name "b".
+
+ 2. Use the low-level ``__call__`` operation on the result of the first
+ operation.
+
+For all operations except the ``__getattribute__`` and
+``__setattribute__`` operations, security proxies have a permission
+value defined by the permission-declaration subsystem. Two special
+permission values indicate that access is either forbidden (never
+allowed) or public (always allowed). For all other permission values,
+the authorization subsystem is used to decide whether the subject has
+the permission for the proxied object. If the subject has the
+permission, then access to the operation is allowed. Otherwise, access
+is denied.
+
+For getting or setting attributes, a proxy has permissions for getting
+and a permission for setting attribute values for a given attribute
+name. As described above, these permissions may be one of the two
+special permission values indicating forbidden or public access, or
+another permission value that must be checked with the authorization
+system.
+
+For all objects, Zope defines the following operations to be always public:
+
+ comparison
+ "__lt__", "__le__", "__eq__", "__gt__", "__ge__", "__ne__"
+
+ hash
+ "__hash__"
+
+ boolean value
+ "__nonzero__"
+
+ class introspection
+ "__class__"
+
+ interface introspection
+ "__providedBy__", "__implements__"
+
+ adaptation
+ "__conform__"
+
+ low-level string representation
+ "__repr__"
+
+The result of an operation on a proxied object is a security proxy
+unless the result is a basic value.
+
+Basic objects
+=============
+
+Basic objects are safe immutable objects that contain only immutable
+subobjects. Examples of basic objects include:
+
+- Strings,
+
+- Integers (long and normal),
+
+- Floating-point objects,
+
+- Date-time objects,
+
+- Boolean objects (True and False), and
+
+- The special (nil) object, None.
+
+Basic objects are safe, so, as described earlier, operations on basic
+objects, other than attribute access, use only information contained
+within the objects or information passed to them. For this reason,
+basic objects cannot be used to access information outside of the
+untrusted interpreter environment.
+
+The decision not to proxy basic objects is largely an optimization.
+It allows low-level safe computation to be performed without
+unnecessary overhead,
+
+Note that a basic object could contain sensitive information, but such
+a basic object would need to be obtained by making a call on a proxied
+object. Therefore, the access to the basic object in the first place
+is mediated by the security functions.
+
+Rationale for mutable safe objects
+==================================
+
+Some safe objects are not basic. For these objects, we proxy the
+objects if they originate from outside of the environment. We do this
+for two reasons:
+
+1. Non-basic objects from outside the environment need to be proxied
+ to prevent unauthorized access to information.
+
+2. We need to prevent un-mediated change of information from outside of
+ the environment.
+
+We don't proxy safe objects created within the environment. This is
+safe to do because such safe objects can contain and provide access to
+information already in the environment. Sometimes the interpreter or
+the interpreted program needs to be able to create simple data
+containers to hold information computed in the course of the program
+execution. Several safe container types are provided for this
+purpose.
+
+.. _proxy-known-issues:
+
+Known Issues With Proxies
+=========================
+
+Security proxies (proxies in general) are not perfect in Python. There
+are some things that they cannot transparently proxy.
+
+.. _isinstance-and-proxies:
+
+isinstance and proxies
+----------------------
+
+A proxied object cannot proxy its type (although it does proxy its ``__class__``):
+
+.. doctest::
+
+ >>> from zope.security.proxy import ProxyFactory
+ >>> class Object(object):
+ ... pass
+ >>> target = Object()
+ >>> target.__class__
+ <class 'Object'>
+ >>> type(target)
+ <class 'Object'>
+ >>> proxy = ProxyFactory(target, None)
+ >>> proxy.__class__
+ <class 'Object'>
+ >>> type(proxy)
+ <... 'zope.security...Proxy...'>
+
+This means that the builtin :func:`isinstance` may return unexpected
+results:
+
+.. doctest::
+
+ >>> isinstance(target, Object)
+ True
+ >>> isinstance(proxy, Object)
+ False
+
+There are two workarounds. The safest is to use
+:func:`zope.security.proxy.isinstance`, which takes specifically this
+into account (in modules that will be dealing with a number of
+proxies, it is common to simply place ``from zope.security.proxy
+import isinstance`` at the top of the file to override the builtin
+:func:`isinstance`; we won't show that here for clarity):
+
+.. doctest::
+
+ >>> import zope.security.proxy
+ >>> zope.security.proxy.isinstance(target, Object)
+ True
+ >>> zope.security.proxy.isinstance(proxy, Object)
+ True
+
+Alternatively, you can manually remove the security proxy (or indeed,
+all proxies) with :func:`zope.security.proxy.removeSecurityProxy` or
+:func:`zope.proxy.removeAllProxies`, respectively, before calling
+:func:`isinstance`:
+
+.. doctest::
+
+ >>> from zope.security.proxy import removeSecurityProxy
+ >>> isinstance(removeSecurityProxy(target), Object)
+ True
+ >>> isinstance(removeSecurityProxy(proxy), Object)
+ True
+
+
+issubclass and proxies
+----------------------
+
+Security proxies will proxy the return value of ``__class__``: it will
+be a proxy around the real class of the proxied value. This causes
+failures with ``issubclass``:
+
+.. doctest::
+
+ >>> from zope.security.proxy import ProxyFactory
+ >>> class Object(object):
+ ... pass
+ >>> target = Object()
+ >>> target.__class__ is Object
+ True
+ >>> proxy = ProxyFactory(target, None)
+ >>> proxy.__class__
+ <class 'Object'>
+ >>> proxy.__class__ is Object
+ False
+ >>> issubclass(proxy.__class__, Object)
+ Traceback (most recent call last):
+ ...
+ TypeError: issubclass() arg 1 must be a class
+
+Although the above is a contrived example, using :class:`abstract base
+classes <abc.ABCMeta>` can cause it to arise quite
+unexpectedly:
+
+.. doctest::
+
+ >>> from collections import Mapping
+ >>> from abc import ABCMeta
+ >>> isinstance(Mapping, ABCMeta)
+ True
+ >>> isinstance(proxy, Mapping)
+ Traceback (most recent call last):
+ ...
+ TypeError: issubclass() arg 1 must be a class
+
+
+In this case, the workarounds described :ref:`above <isinstance-and-proxies>` also work:
+
+.. doctest::
+
+ >>> zope.security.proxy.isinstance(proxy, Mapping)
+ False
+ >>> isinstance(removeSecurityProxy(proxy), Mapping)
+ False
+
+.. We need to clean up the caching that ABC does on
+.. pure-python platforms to make sure that we get where
+.. we expect to be when we construct LogRecord; otherwise
+.. the ProxyMetaclass may be in the negative cache, bypassing
+.. the issubclass() calls we expect
+
+.. doctest::
+ :hide:
+
+ >>> ABCMeta._abc_invalidation_counter += 1
+
+logging
+~~~~~~~
+
+Starting with `Python 2.7.7 <https://bugs.python.org/issue21172>`_,
+the :class:`logging.LogRecord` makes exactly the above ``isinstance``
+call:
+
+.. doctest::
+
+ >>> from logging import LogRecord
+ >>> LogRecord("name", 1, "/path/to/file", 1,
+ ... "The message %s", (proxy,), None)
+ Traceback (most recent call last):
+ ...
+ TypeError: issubclass() arg 1 must be a class
+
+`Possible workarounds include <https://github.com/zopefoundation/zope.security/issues/26>`_:
+
+- Carefully removing security proxies of objects before passing them
+ to the logging system.
+- Monkey-patching the logging system to use
+ :func:`zope.security.proxy.isinstance` which does this
+ automatically::
+
+ import zope.security.proxy
+ import logging
+ logging.isinstance = zope.security.proxy.isinstance
+- Using :func:`logging.setLogRecordfactory` to set a custom
+ ``LogRecord`` subclass that unwraps any security proxies before they
+ are given to the super class. Note that this is only available on
+ Python 3. On Python 2, it might be possible to achieve a similar
+ result with a custom :func:`logger class <logging.setLoggerClass>`:
+
+.. doctest::
+
+ >>> from zope.security.proxy import removeSecurityProxy
+ >>> class UnwrappingLogRecord(LogRecord):
+ ... def __init__(self, name, level, pathname, lineno,
+ ... msg, args, exc_info, *largs, **kwargs):
+ ... args = [removeSecurityProxy(x) for x in args]
+ ... LogRecord.__init__(self, name, level, pathname,
+ ... lineno, msg, args, exc_info, *largs, **kwargs)
+ ... def __repr__(self):
+ ... return '<UnwrappingLogRecord>'
+ >>> UnwrappingLogRecord("name", 1, "/path/to/file", 1,
+ ... "The message %s", (proxy,), None)
+ <UnwrappingLogRecord>
+
+
+Each specific application will have to determine what solution is
+correct for its security model.
diff --git a/src/zope/security/adapter.py b/src/zope/security/adapter.py
index f92ac34..661e49d 100644
--- a/src/zope/security/adapter.py
+++ b/src/zope/security/adapter.py
@@ -19,12 +19,13 @@ from zope.security.proxy import removeSecurityProxy
from zope.location import ILocation, LocationProxy
def assertLocation(adapter, parent):
- """Assert locatable adapters.
+ """
+ Assert locatable adapters.
- This function asserts that the adapter get location-proxied if
- it doesn't provide ILocation itself. Further more the returned
- locatable adapter get its parent set if its __parent__ attribute
- is currently None.
+ This function asserts that the adapter get location-proxied if it
+ doesn't provide :class:`zope.location.interfaces.ILocation`
+ itself. Furthermore, the returned locatable adapter get its parent
+ set if its ``__parent__`` attribute is currently None.
"""
# handle none-locatable adapters (A)
if not ILocation.providedBy(adapter):
@@ -42,7 +43,8 @@ def assertLocation(adapter, parent):
class LocatingTrustedAdapterFactory(object):
- """Adapt an adapter factory to provide trusted and (locatable) adapters.
+ """
+ Adapt an adapter factory to provide trusted and (locatable) adapters.
Trusted adapters always adapt unproxied objects. If asked to
adapt any proxied objects, it will unproxy them and then
@@ -50,10 +52,12 @@ class LocatingTrustedAdapterFactory(object):
security-proxied before (N).
Further locating trusted adapters provide a location for protected
- adapters only (S). If such a protected adapter itself does not provide
- ILocation it is wrapped within a location proxy and it parent will
- be set. If the adapter does provide ILocation and it's __parent__ is None,
- we set the __parent__ to the adapter's context:
+ adapters only (S). If such a protected adapter itself does not
+ provide ILocation it is wrapped within a location proxy and it
+ parent will be set. If the adapter does provide
+ :class:`zope.location.interfaces.ILocation` and its
+ ``__parent__`` is None, we set the ``__parent__`` to the adapter's
+ context.
"""
def __init__(self, factory):
self.factory = factory
@@ -83,15 +87,17 @@ class LocatingTrustedAdapterFactory(object):
class TrustedAdapterFactory(LocatingTrustedAdapterFactory):
- """Adapt an adapter factory to provide trusted adapters.
+ """
+ Adapt an adapter factory to provide trusted adapters.
Trusted adapters always adapt unproxied objects. If asked to
adapt any proxied objects, it will unproxy them and then
security-proxy the resulting adapter unless the objects where not
security-proxied before.
- If the adapter does provide ILocation and it's __parent__ is None,
- we set the __parent__ to the adapter's context.
+ If the adapter does provide
+ :class:`zope.location.interfaces.ILocation` and its ``__parent__``
+ is None, we set the ``__parent__`` to the adapter's context.
"""
# do not location-proxy the adapter
@@ -100,17 +106,20 @@ class TrustedAdapterFactory(LocatingTrustedAdapterFactory):
class LocatingUntrustedAdapterFactory(object):
- """Adapt an adapter factory to provide locatable untrusted adapters
+ """
+ Adapt an adapter factory to provide locatable untrusted adapters
Untrusted adapters always adapt proxied objects. If any permission
- other than zope.Public is required, untrusted adapters need a location
- in order that the local authentication mechanism can be inovked
- correctly.
-
- If the adapter does not provide ILocation, we location proxy it and
- set the parent. If the adapter does provide ILocation and
- it's __parent__ is None, we set the __parent__ to the adapter's
- context only:
+ other than :const:`zope.Public
+ <zope.security.interfaces.PUBLIC_PERMISSION_NAME>` is required,
+ untrusted adapters need a location in order that the local
+ authentication mechanism can be invoked correctly.
+
+ If the adapter does not provide
+ :class:`zope.location.interfaces.ILocation`, we location proxy it
+ and set the parent. If the adapter does provide ``ILocation`` and
+ its ``__parent__`` is None, we set the ``__parent__`` to the
+ adapter's context only.
"""
def __init__(self, factory):
diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py
index 7de50d5..79975d4 100644
--- a/src/zope/security/checker.py
+++ b/src/zope/security/checker.py
@@ -11,17 +11,49 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Security Checkers
+"""
+Security Checkers.
+
+This module contains the primary implementations of
+:class:`zope.security.interfaces.IChecker` (:class:`Checker`,
+:class:`MultiChecker`, :func:`NamesChecker`) and
+:class:`zope.security.interfaces.IProxyFactory` (:func:`ProxyFactory`).
+
+It also defines helpers for permission checking (:func:`canAccess`,
+:func:`canWrite`) and getting checkers
+(:func:`getCheckerForInstancesOf`, :func:`selectChecker`).
+
+This module is accelerated with a C implementation on CPython by
+default. If the environment variable ``PURE_PYTHON`` is set (to any
+value) before this module is imported, the C extensions will be
+bypassed and the reference Python implementations will be used. This
+can be helpful for debugging and tracing.
-You can set the environment variable ZOPE_WATCH_CHECKERS to get additional
-security checker debugging output on the standard error.
+Debugging Permissions Problems
+==============================
-Setting ZOPE_WATCH_CHECKERS to 1 will display messages about unauthorized or
+You can set the environment variable ``ZOPE_WATCH_CHECKERS`` before
+this module is imported to get additional security checker debugging
+output on the standard error.
+
+Setting ``ZOPE_WATCH_CHECKERS`` to 1 will display messages about unauthorized or
forbidden attribute access. Setting it to a larger number will also display
messages about granted attribute access.
-Note that the ZOPE_WATCH_CHECKERS mechanism will eventually be
+Note that the ``ZOPE_WATCH_CHECKERS`` mechanism may eventually be
replaced with a more general security auditing mechanism.
+
+.. seealso:: :class:`CheckerLoggingMixin`, :class:`WatchingChecker`, :class:`WatchingCombinedChecker`
+
+API
+===
+
+.. py:data:: CheckerPublic
+
+ The special constant that indicates that no permission
+ checking needs to be done.
+
+.. autofunction:: selectChecker
"""
import abc
import os
@@ -156,15 +188,22 @@ def canAccess(obj, name):
@implementer(INameBasedChecker)
class CheckerPy(object):
+ """
+ The Python reference implementation of
+ :class:`zope.security.interfaces.INameBasedChecker`.
+
+ Ordinarily there will be no reason to ever explicitly use this class;
+ instead use the class assigned to :class:`Checker`.
+ """
def __init__(self, get_permissions, set_permissions=None):
"""Create a checker
A dictionary must be provided for computing permissions for
names. The dictionary get will be called with attribute names
- and must return a permission id, None, or the special marker,
- CheckerPublic. If None is returned, then access to the name is
- forbidden. If CheckerPublic is returned, then access will be
+ and must return a permission ID, None, or the special marker,
+ :const:`CheckerPublic`. If None is returned, then access to the name is
+ forbidden. If :const:`CheckerPublic` is returned, then access will be
granted without checking a permission.
An optional setattr dictionary may be provided for checking
@@ -295,12 +334,14 @@ class Global(object):
return "%s(%s,%s)" % (self.__class__.__name__,
self.__name__, self.__module__)
-# Marker for public attributes
+
CheckerPublic = Global('CheckerPublic')
CP_HACK_XXX = CheckerPublic
# Now we wrap it in a security proxy so that it retains its
# identity when it needs to be security proxied.
+# XXX: This means that we can't directly document it with
+# sphinx because issubclass() will fail.
d = {}
CheckerPublic = Proxy(CheckerPublic, Checker(d)) # XXX uses CheckerPy
d['__reduce__'] = CheckerPublic
@@ -316,7 +357,7 @@ def NamesChecker(names=(), permission_id=CheckerPublic, **__kw__):
A sequence of names is given as the first argument. If a second
argument, permission_id, is given, it is the permission required
- to access the names. Additional names and permission ids can be
+ to access the names. Additional names and permission IDs can be
supplied as keyword arguments.
"""
@@ -330,10 +371,15 @@ def NamesChecker(names=(), permission_id=CheckerPublic, **__kw__):
return Checker(data)
def InterfaceChecker(interface, permission_id=CheckerPublic, **__kw__):
+ """
+ Create a :func:`NamesChecker` for all the names defined in the *interface*
+ (a subclass of :class:`zope.interface.Interface`).
+ """
return NamesChecker(interface.names(all=True), permission_id, **__kw__)
def MultiChecker(specs):
- """Create a checker from a sequence of specifications
+ """
+ Create a checker from a sequence of specifications
A specification is:
@@ -341,12 +387,12 @@ def MultiChecker(specs):
o a sequence of names or an interface
- o a permission id
+ o a permission ID
All the names in the sequence of names or the interface are
protected by the permission.
- - A dictionoid (having an items method), with items that are
+ - A dictionary (having an items method), with items that are
name/permission-id pairs.
"""
data = {}
@@ -409,8 +455,8 @@ DEFINABLE_TYPES = CLASS_TYPES + (types.ModuleType,)
def defineChecker(type_, checker):
"""Define a checker for a given type of object
- The checker can be a Checker, or a function that, when called with
- an object, returns a Checker.
+ The checker can be a :class:`Checker`, or a function that, when called with
+ an object, returns a :class:`Checker`.
"""
if not isinstance(type_, DEFINABLE_TYPES):
raise TypeError(
@@ -463,16 +509,23 @@ class CombinedChecker(Checker):
The following table describes the result of a combined checker in detail.
- checker1 checker2 CombinedChecker(checker1, checker2)
- ------------------ ------------------ -----------------------------------
- ok anything ok (checker2 is never called)
- Unauthorized ok ok
- Unauthorized Unauthorized Unauthorized
- Unauthorized ForbiddenAttribute Unauthorized
- ForbiddenAttribute ok ok
- ForbiddenAttribute Unauthorized Unauthorized
- ForbiddenAttribute ForbiddenAttribute ForbiddenAttribute
- ------------------ ------------------ -----------------------------------
+ +--------------------+--------------------+-------------------------------------+
+ | checker1 | checker2 | CombinedChecker(checker1, checker2) |
+ +====================+====================+=====================================+
+ | ok | anything | ok (checker 2 never called) |
+ +--------------------+--------------------+-------------------------------------+
+ | Unathorized | ok | ok |
+ +--------------------+--------------------+-------------------------------------+
+ | Unauthorized | Unauthorized | Unauthorized |
+ +--------------------+--------------------+-------------------------------------+
+ | Unauthorized | ForbiddenAttribute | Unauthorized |
+ +--------------------+--------------------+-------------------------------------+
+ | ForbiddenAttribute | ok | ok |
+ +--------------------+--------------------+-------------------------------------+
+ | ForbiddenAttribute | Unauthorized | Unauthorized |
+ +--------------------+--------------------+-------------------------------------+
+ | ForbiddenAttribute | ForbiddenAttribute | ForbiddenAttribute |
+ +--------------------+--------------------+-------------------------------------+
"""
def __init__(self, checker1, checker2):
@@ -509,15 +562,18 @@ class CombinedChecker(Checker):
raise unauthorized_exception
class CheckerLoggingMixin(object):
- """Debugging mixin for checkers.
+ """
+ Debugging mixin for checkers.
Prints verbose debugging information about every performed check to
- sys.stderr.
+ :data:`sys.stderr`.
- If verbosity is set to 1, only displays Unauthorized and Forbidden messages.
- If verbosity is set to a larger number, displays all messages.
"""
+ #: If set to 1 (the default), only displays ``Unauthorized`` and
+ #: ``Forbidden`` messages. If verbosity is set to a larger number,
+ #: displays all messages. Normally this is controlled via the environment
+ #: variable ``ZOPE_WATCH_CHECKERS``.
verbosity = 1
_file = sys.stderr
@@ -586,8 +642,18 @@ class CheckerLoggingMixin(object):
# We have to be careful with the order of inheritance
# here. See https://github.com/zopefoundation/zope.security/issues/8
class WatchingChecker(CheckerLoggingMixin, Checker):
+ """
+ A checker that will perform verbose logging. This will be set
+ as the default when ``ZOPE_WATCH_CHECKERS`` is set when this
+ module is imported.
+ """
verbosity = WATCH_CHECKERS
class WatchingCombinedChecker(CombinedChecker, WatchingChecker):
+ """
+ A checker that will perform verbose logging. This will be set
+ as the default when ``ZOPE_WATCH_CHECKERS`` is set when this
+ module is imported.
+ """
verbosity = WATCH_CHECKERS
if WATCH_CHECKERS: # pragma: no cover
@@ -606,6 +672,13 @@ def _instanceChecker(inst):
return _checkers.get(inst.__class__, _defaultChecker)
def moduleChecker(module):
+ """
+ Return the :class:`zope.security.interfaces.IChecker` defined for the
+ *module*, if any.
+
+ .. seealso:: :func:`zope.security.metaconfigure.protectModule`
+ To define module protections.
+ """
return _checkers.get(module)
diff --git a/src/zope/security/interfaces.py b/src/zope/security/interfaces.py
index 81addc2..4c62bdc 100644
--- a/src/zope/security/interfaces.py
+++ b/src/zope/security/interfaces.py
@@ -11,7 +11,43 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Interfaces for security machinery.
+"""
+Interfaces for security machinery.
+
+These can be categorized into a few different groups of related objects.
+
+* Exceptions
+
+ - :class:`IUnauthorized`
+ - :class:`IForbidden`
+ - :class:`IForbiddenAttribute`
+ - :class:`NoInteraction`
+
+* Utilities
+
+ - :class:`ISecurityManagement`
+ - :class:`ISecurityChecking`
+ - :class:`ISecurityProxyFactory`
+ - :class:`IChecker`
+ - :class:`INameBasedChecker`
+ - :class:`ISecurityPolicy`
+
+* Principals
+
+ - :class:`IInteraction`
+ - :class:`IParticipation`
+ - :class:`IInteractionManagement`
+ - :class:`IPrincipal`
+ - :class:`IGroupAwarePrincipal`
+ - :class:`IGroupClosureAwarePrincipal`
+ - :class:`IGroup`
+ - :class:`IMemberGetterGroup`
+ - :class:`IMemberAwareGroup`
+ - :class:`IPermission`
+
+Anywhere that an API is documented as accepting a permission, it
+means the name of the permission, or the special object
+:class:`zope.security.checker.CheckerPublic`.
"""
from zope.interface import Interface, Attribute, implementer
@@ -26,31 +62,57 @@ from zope.security.i18n import ZopeMessageFactory as _
PUBLIC_PERMISSION_NAME = 'zope.Public'
class IUnauthorized(IException):
- pass
+ """
+ The action is not authorized.
+
+ Implemented in :class:`Unauthorized`.
+ """
@implementer(IUnauthorized)
class Unauthorized(Exception):
- """Some user wasn't allowed to access a resource"""
+ """
+ Some user wasn't allowed to access a resource.
+
+ Default implementation of :class:`IUnauthorized`.
+ """
class IForbidden(IException):
- pass
+ """
+ A resource cannot be accessed under any circumstances
+
+ Implemented in :class:`Forbidden`.
+ """
@implementer(IForbidden)
class Forbidden(Exception):
- """A resource cannot be accessed under any circumstances
+ """
+ A resource cannot be accessed under any circumstances
+
+ Default implementation if :class:`IForbidden`.
"""
class IForbiddenAttribute(IForbidden, IAttributeError):
- pass
+ """
+ An attribute is unavailable because it is forbidden (private).
+
+ Implemented in :class:`ForbiddenAttribute`.
+ """
@implementer(IForbiddenAttribute)
class ForbiddenAttribute(Forbidden, AttributeError):
- """An attribute is unavailable because it is forbidden (private)
+ """
+ An attribute is unavailable because it is forbidden (private).
+
+ Default implementation of :class:`IForbiddenAttribute`.
"""
class ISecurityManagement(Interface):
- """Public security management API."""
+ """
+ Public security management API.
+
+ This is implemented by :mod:`zope.security.management`.
+ """
def getSecurityPolicy():
"""Get the system default security policy."""
@@ -64,28 +126,36 @@ class ISecurityManagement(Interface):
class ISecurityChecking(Interface):
- """Public security API."""
+ """
+ Public security API.
+ """
def checkPermission(permission, object, interaction=None):
- """Return whether security policy allows permission on object.
-
- 'permission' is permission name.
-
- 'object' is the object being accessed according to the permission.
+ """
+ Return whether security policy allows permission on object.
- 'interaction' is an interaction, providing access to information
- such as authenticated principals. If it is None, the current
- interaction is used.
+ :param str permission: The permission name.
+ :param object: The object being accessed according to the permission.
+ :keyword interaction: An :class:`IInteraction`, providing access to information
+ such as authenticated principals. If it is None, the current
+ interaction is used.
"""
class ISecurityProxyFactory(Interface):
+ """
+ A factory for creating security-proxied objects.
+
+ See :class:`zope.security.checker.ProxyFactory` for the
+ default implementation.
+ """
def __call__(object, checker=None):
- """Create a security proxy
+ """
+ Create a security proxy
- If a checker is given, then use it, otherwise, try to figure
- out a checker.
+ If a checker (:class:`IChecker`) is given, then use it,
+ otherwise, try to figure out a checker.
If the object is already a security proxy, then it will be
returned.
@@ -93,93 +163,131 @@ class ISecurityProxyFactory(Interface):
class IChecker(Interface):
- """Security-proxy plugin objects that implement low-level checks
+ """
+ Security-proxy plugin objects that implement low-level checks.
The checker is responsible for creating proxies for
- operation return values, via the proxy method.
+ operation return values, via the ``proxy`` method.
- There are check_getattr() and check_setattr() methods for checking
- getattr and setattr, and a check() method for all other operations.
+ There are :meth:`check_getattr` and :meth:`check_setattr` methods
+ for checking getattr and setattr, and a :meth:`check` method for all
+ other operations.
- The check methods may raise errors. They return no value.
+ The check methods will raise errors if access is not allowed.
+ They return no value.
- Example (for __getitem__):
+ Example (for ``__getitem__``)::
- checker.check(ob, \"__getitem__\")
+ checker.check(ob, "__getitem__")
return checker.proxy(ob[key])
+
+ .. seealso:: :mod:`zope.security.checker`
"""
def check_getattr(ob, name):
- """Check whether attribute access is allowed.
-
- May raise Unauthorized or Forbidden. Returns no value.
+ """
+ Check whether attribute access is allowed.
- If a checker implements __setitem__, then __setitem__ will be
- called rather than check_getattr to check whether an attribute
- access is allowed. This is a hack that allows significantly
+ If a checker implements ``__setitem__``, then ``__setitem__``
+ will be called rather than ``check`` to ascertain whether an
+ operation is allowed. This is a hack that allows significantly
greater performance due to the fact that low-level operator
access is much faster than method access.
+
+ :raises: :class:`Unauthorized`
+ :raises: :class:`Forbidden`
+ :return: Nothing
"""
def check_setattr(ob, name):
- """Check whether attribute assignment is allowed.
+ """
+ Check whether attribute assignment is allowed.
- May raise Unauthorized or Forbidden. Returns no value.
+ If a checker implements ``__setitem__``, then ``__setitem__``
+ will be called rather than ``check`` to ascertain whether an
+ operation is allowed. This is a hack that allows significantly
+ greater performance due to the fact that low-level operator
+ access is much faster than method access.
+
+ :raises: :class:`Unauthorized`
+ :raises: :class:`Forbidden`
+ :return: Nothing
"""
def check(ob, operation):
- """Check whether operation is allowed.
+ """
+ Check whether *operation* is allowed.
The operation name is the Python special method name,
e.g. "__getitem__".
May raise Unauthorized or Forbidden. Returns no value.
- If a checker implements __setitem__, then __setitem__ will be
- called rather than check to check whether an operation is
- allowed. This is a hack that allows significantly greater
- performance due to the fact that low-level operator access is
- much faster than method access.
+ If a checker implements ``__setitem__``, then ``__setitem__``
+ will be called rather than ``check`` to ascertain whether an
+ operation is allowed. This is a hack that allows significantly
+ greater performance due to the fact that low-level operator
+ access is much faster than method access.
+
+ :raises: :class:`Unauthorized`
+ :raises: :class:`Forbidden`
+ :return: Nothing
"""
def proxy(value):
- """Return a security proxy for the value.
+ """
+ Return a security proxy for the *value*.
- If a checker implements __getitem__, then __getitem__ will be
- called rather than proxy to proxy the value. This is a hack
- that allows significantly greater performance due to the fact
- that low-level operator access is much faster than method
- access.
+ If a checker implements ``__getitem__``, then ``__getitem__``
+ will be called rather than ``proxy`` to proxy the value. This
+ is a hack that allows significantly greater performance due to
+ the fact that low-level operator access is much faster than
+ method access.
"""
class INameBasedChecker(IChecker):
- """Security checker that uses permissions to check attribute access."""
+ """
+ Security checker that uses permissions to check attribute
+ access.
+ """
def permission_id(name):
- """Return the permission used to check attribute access on name.
+ """
+ Return the permission used to check attribute access on *name*.
- This permission is used by both check and check_getattr.
+ This permission is used by both :meth:`check` and :meth:`check_getattr`.
"""
def setattr_permission_id(name):
- """Return the permission used to check attribute assignment on name.
+ """
+ Return the permission used to check attribute assignment on *name*.
- This permission is used by check_setattr.
+ This permission is used by :meth:`check_setattr`.
"""
class ISecurityPolicy(Interface):
+ """
+ A factory to get :class:`IInteraction` objects.
+
+ .. seealso:: :mod:`zope.security.simplepolicies`
+ For default implementations.
+ """
def __call__(participation=None):
- """Creates a new interaction for a given request.
+ """
+ Creates and returns a new :class:`IInteraction` for a given
+ request.
- If participation is not None, it is added to the new interaction.
+ If *participation* is not None, it is added to the new interaction.
"""
class IInteraction(Interface):
- """A representation of an interaction between some actors and the system.
+ """
+ A representation of an interaction between some actors and the
+ system.
"""
participations = Attribute("""An iterable of participations.""")
@@ -193,16 +301,20 @@ class IInteraction(Interface):
def checkPermission(permission, object):
"""Return whether security context allows permission on object.
- Arguments:
- permission -- A permission name
- object -- The object being accessed according to the permission
+ :param str permission: A permission name
+ :param object: The object being accessed according to the permission
+ :return: Whether the access is allowed or not.
+ :rtype: bool
"""
class IParticipation(Interface):
+ """
+ A single participant in an interaction.
+ """
interaction = Attribute("The interaction")
- principal = Attribute("The authenticated principal")
+ principal = Attribute("The authenticated :class:`IPrincipal`")
class NoInteraction(Exception):
@@ -210,50 +322,58 @@ class NoInteraction(Exception):
"""
class IInteractionManagement(Interface):
- """Interaction management API.
+ """
+ Interaction management API.
Every thread has at most one active interaction at a time.
+
+ .. seealso:: :mod:`zope.security.management`
+ That module provides the default implementation.
"""
def newInteraction(participation=None):
- """Start a new interaction.
+ """
+ Start a new interaction.
- If participation is not None, it is added to the new interaction.
+ If *participation* is not None, it is added to the new interaction.
Raises an error if the calling thread already has an interaction.
"""
def queryInteraction():
- """Return the current interaction.
+ """
+ Return the current interaction.
Return None if there is no interaction.
"""
def getInteraction():
- """Return the current interaction.
+ """
+ Return the current interaction.
- Raise NoInteraction if there isn't a current interaction.
+ :raise NoInteraction: if there isn't a current interaction.
"""
def endInteraction():
- """End the current interaction.
+ """
+ End the current interaction.
Does nothing if there is no interaction.
"""
class IPrincipal(Interface):
- """Principals are security artifacts that execute actions in a security
- environment.
+ """
+ Principals are security artifacts that execute actions in a
+ security environment.
- The most common examples of principals include user and group objects.
+ The most common examples of principals include user and group
+ objects.
- It is likely that IPrincipal objects will have associated views
- used to list principals in management interfaces. For example, a
- system in which other meta-data are provided for principals might
- extend IPrincipal and register a view for the extended interface
- that displays the extended information. We'll probably want to
- define a standard view name (e.g. 'inline_summary') for this
- purpose.
+ It is likely that ``IPrincipal`` objects will have associated
+ views used to list principals in management interfaces. For
+ example, a system in which other meta-data are provided for
+ principals might extend ``IPrincipal`` and register a view for the
+ extended interface that displays the extended information.
"""
id = TextLine(
@@ -275,33 +395,47 @@ class IPrincipal(Interface):
class IGroupAwarePrincipal(IPrincipal):
- """Group aware principal interface
- Extends IPrincipal to contain group information.
+ """
+ Group aware principal interface.
+
+ Extends ``IPrincipal`` to contain direct group information.
"""
groups = Attribute(
- 'An iterable of groups to which the principal directly belongs')
+ 'An iterable of :class:`IGroup` objects to which the principal directly belongs')
class IGroupClosureAwarePrincipal(IGroupAwarePrincipal):
+ """
+ A group-aware principal that can recursively flatten the membership
+ of groups to return all the groups.
+ """
allGroups = Attribute(
"An iterable of the full closure of the principal's groups.")
class IGroup(IPrincipal):
- """Group of principals
+ """
+ Group of principals
"""
class IMemberGetterGroup(IGroup):
- """a group that can get its members"""
+ """
+ A group that can get its members.
+ """
def getMembers():
- """return an iterable of the members of the group"""
+ """Return an iterable of the members of the group"""
class IMemberAwareGroup(IMemberGetterGroup):
- """a group that can both set and get its members."""
+ """
+ A group that can both set and get its members.
+ """
def setMembers(value):
- """set members of group to the principal ids in the iterable value"""
+ """
+ Set members of group to the principal IDs in the iterable
+ *value*.
+ """
class IPermission(Interface):
"""A permission object."""
diff --git a/src/zope/security/management.py b/src/zope/security/management.py
index 7a77f1a..0c037d0 100644
--- a/src/zope/security/management.py
+++ b/src/zope/security/management.py
@@ -11,7 +11,11 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Default 'ISecurityManagement' and 'IInteractionManagement' implementation
+"""
+Default :class:`zope.security.interfaces.ISecurityManagement` and
+:class:`zope.security.interfaces.IInteractionManagement` implementation.
+
+Note that this module itself provides those interfaces.
"""
from zope.interface import moduleProvides
@@ -58,6 +62,7 @@ def setSecurityPolicy(aSecurityPolicy):
#
def queryInteraction():
+ """Return a current interaction, if there is one."""
return getattr(thread_local, 'interaction', None)
def getInteraction():
@@ -70,7 +75,10 @@ def getInteraction():
class ExistingInteraction(ValueError,
AssertionError, #BBB
):
- pass
+ """
+ The exception that :func:`newInteraction` will raise if called
+ during an existing interaction.
+ """
def newInteraction(*participations):
"""Start a new interaction."""
@@ -109,16 +117,16 @@ def restoreInteraction():
def checkPermission(permission, object, interaction=None):
"""Return whether security policy allows permission on object.
- 'permission' is a permission name.
-
- 'object' is the object being accessed according to the permission.
-
- 'interaction' is an interaction, providing access to information
- such as authenticated principals. If it is None, the current
- interaction is used.
-
- checkPermission is guaranteed to return True if permission is
- CheckerPublic or None.
+ :param str permission: A permission name.
+ :param object: The object being accessed according to the permission.
+ :param interaction: An interaction, providing access to information
+ such as authenticated principals. If it is None, the current
+ interaction is used.
+ :return: A boolean value. ``checkPermission`` is guaranteed to
+ return ``True`` if *permission* is
+ :data:`zope.security.checker.CheckerPublic` or ``None``.
+ :raise NoInteraction: If there is no current interaction and no
+ interaction argument was given.
"""
if permission is CheckerPublic or permission is None:
return True
diff --git a/src/zope/security/metadirectives.py b/src/zope/security/metadirectives.py
index bd1f793..c0830d6 100644
--- a/src/zope/security/metadirectives.py
+++ b/src/zope/security/metadirectives.py
@@ -192,4 +192,4 @@ class IRequire(Interface):
permission = Permission(
title=u"Permission ID",
- description=u"The id of the permission to require.")
+ description=u"The ID of the permission to require.")
diff --git a/src/zope/security/permission.py b/src/zope/security/permission.py
index 9854e3d..b54d285 100644
--- a/src/zope/security/permission.py
+++ b/src/zope/security/permission.py
@@ -31,6 +31,9 @@ from zope.security.interfaces import PUBLIC_PERMISSION_NAME as zope_Public
@implementer(IPermission)
class Permission(object):
+ """
+ Default implementation of :class:`zope.security.interfaces.IPermission`.
+ """
def __init__(self, id, title="", description=""):
self.id = id
@@ -38,22 +41,26 @@ class Permission(object):
self.description = description
def checkPermission(context, permission_id):
- """Check whether a given permission exists in the provided context.
+ """
+ Check whether a given permission object exists in the provided
+ context as a utility.
"""
if permission_id is CheckerPublic:
return
if not queryUtility(IPermission, permission_id, context=context):
- raise ValueError("Undefined permission id", permission_id)
+ raise ValueError("Undefined permission ID", permission_id)
def allPermissions(context=None):
- """Get the ids of all defined permissions
+ """
+ Get the IDs of all defined permission object utilities.
"""
for name, _permission in getUtilitiesFor(IPermission, context):
if name != zope_Public:
yield name
def PermissionsVocabulary(context=None):
- """A vocabulary of permission IDs.
+ """
+ A vocabulary of permission IDs.
Term values are permissions, while term tokens are permission IDs.
"""
@@ -65,13 +72,18 @@ def PermissionsVocabulary(context=None):
directlyProvides(PermissionsVocabulary, IVocabularyFactory)
def PermissionIdsVocabulary(context=None):
- """A vocabulary of permission IDs.
+ """
+ A vocabulary of permission IDs.
- Term values are the permission ID strings except for 'zope.Public', which
- is the global permission CheckerPublic.
+ Term values are the permission ID strings except for
+ :data:`zope.Public
+ <zope.security.interfaces.PUBLIC_PERMISSION_NAME>`, which is the
+ global permission :data:`zope.security.checker.CheckerPublic`.
- Term titles are the permission ID strings except for 'zope.Public', which
- is shortened to 'Public'.
+ Term titles are the permission ID strings except for
+ :data:`zope.Public
+ <zope.security.interfaces.PUBLIC_PERMISSION_NAME>`, which is
+ shortened to 'Public'.
Terms are sorted by title except for 'Public', which always appears as
the first term.
diff --git a/src/zope/security/proxy.py b/src/zope/security/proxy.py
index 8f6d7c3..10f0353 100644
--- a/src/zope/security/proxy.py
+++ b/src/zope/security/proxy.py
@@ -11,7 +11,10 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Helper functions for Proxies.
+"""
+Helper functions for proxies.
+
+.. seealso:: :ref:`proxy-known-issues`
"""
import functools
import sys
@@ -60,6 +63,16 @@ def _fmt_address(obj):
class ProxyPy(PyProxyBase):
+ """
+ The pure-Python reference implementation of a security proxy.
+
+ This should normally not be created directly, instead use the
+ :func:`~.ProxyFactory`.
+
+ You can choose to use this implementation instead of the C implementation
+ by default by setting the ``PURE_PYTHON`` environment variable before
+ :mod:`zope.security` is imported.
+ """
__slots__ = ('_wrapped', '_checker')
def __new__(cls, value, checker):
@@ -394,7 +407,7 @@ def getTestProxyItems(proxy):
def isinstance(object, cls):
- """Test whether an object is an instance of a type.
+ """Test whether an *object* is an instance of a type.
This works even if the object is security proxied.
"""
diff --git a/src/zope/security/simplepolicies.py b/src/zope/security/simplepolicies.py
index b2e8980..d1617ce 100644
--- a/src/zope/security/simplepolicies.py
+++ b/src/zope/security/simplepolicies.py
@@ -11,7 +11,13 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Simple 'ISecurityPolicy' implementations.
+"""
+Simple :class:`zope.security.interfaces.ISecurityPolicy` implementations.
+
+As a reminder, ``ISecurityPolicy`` objects are factories for producing
+:class:`zope.security.interfaces.IInteraction` objects. That means
+that the classes themselves are implementations of
+``ISecurityPolicy``.
"""
import zope.interface
@@ -25,7 +31,8 @@ from zope.security._definitions import system_user
@zope.interface.provider(ISecurityPolicy)
class ParanoidSecurityPolicy(object):
"""
- Prohibit all access by any non-system principal, unless the item is public.
+ Prohibit all access by any non-system principal, unless the item
+ is :data:`public <zope.security.checker.CheckerPublic>`.
This means that if there are no participations (and hence no
principals), then access is allowed.
diff --git a/src/zope/security/testing.py b/src/zope/security/testing.py
index aae9b53..bd4b889 100644
--- a/src/zope/security/testing.py
+++ b/src/zope/security/testing.py
@@ -11,7 +11,8 @@
# FOR A PARTICULAR PURPOSE.
#
##############################################################################
-"""Testing support code
+"""
+Testing support code.
This module provides some helper/stub objects for setting up interactions.
"""
@@ -38,6 +39,9 @@ output_checker = renormalizing.RENormalizing(rules)
@interface.implementer(interfaces.IPrincipal)
class Principal(object):
+ """
+ A trivial implementation of :class:`zope.security.interfaces.IPrincipal`.
+ """
def __init__(self, id, title=None, description='', groups=None):
self.id = id
@@ -50,14 +54,19 @@ class Principal(object):
@interface.implementer(interfaces.IParticipation)
class Participation(object):
-
+ """
+ A trivial implementation of :class:`zope.security.interfaces.IParticipation`.
+ """
def __init__(self, principal):
self.principal = principal
self.interaction = None
def addCheckerPublic():
- """Add the CheckerPublic permission as 'zope.Public'"""
+ """
+ Add the CheckerPublic permission as :data:`zope.Public
+ <zope.security.interfaces.PUBLIC_PERMISSION_NAME>`.
+ """
perm = Permission(
PUBLIC_PERMISSION_NAME,
@@ -74,6 +83,12 @@ def addCheckerPublic():
return perm
def create_interaction(principal_id, **kw):
+ """
+ Create a new interaction for the given principal ID, make it the
+ :func:`current interaction
+ <zope.security.management.newInteraction>`, and return the
+ :class:`Principal` object.
+ """
principal = Principal(principal_id, **kw)
participation = Participation(principal)
zope.security.management.newInteraction(participation)
@@ -82,6 +97,10 @@ def create_interaction(principal_id, **kw):
@contextlib.contextmanager
def interaction(principal_id, **kw):
+ """
+ A context manager for running an interaction for the given
+ principal ID.
+ """
if zope.security.management.queryInteraction():
# There already is an interaction. Great. Leave it alone.
yield
diff --git a/src/zope/security/zcml.py b/src/zope/security/zcml.py
index 777a4fd..78ef5cc 100644
--- a/src/zope/security/zcml.py
+++ b/src/zope/security/zcml.py
@@ -75,8 +75,8 @@ class IPermissionDirective(Interface):
"""Define a new security object."""
id = Id(
- title=u"Id",
- description=u"Id as which this object will be known and used.",
+ title=u"ID",
+ description=u"ID as which this object will be known and used.",
required=True)
title = MessageID(
@@ -101,12 +101,12 @@ class IRedefinePermission(Interface):
from_ = Permission(
title=u"Original permission",
- description=u"Original permission id to redefine.",
+ description=u"Original permission ID to redefine.",
required=True)
to = Permission(
title=u"Substituted permission",
- description=u"Substituted permission id.",
+ description=u"Substituted permission ID.",
required=True)
def redefinePermission(_context, from_, to):