From 27f1d8b4358d6651f28e239bbc23a7e38f9cd789 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 09:22:51 -0500 Subject: cross-references and interface cleanups for interfaces.py/rst --- docs/api/interfaces.rst | 99 +------------- src/zope/security/interfaces.py | 294 ++++++++++++++++++++++++++++------------ 2 files changed, 214 insertions(+), 179 deletions(-) diff --git a/docs/api/interfaces.rst b/docs/api/interfaces.rst index cf4e757..58d7756 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -1,98 +1,5 @@ -:mod:`zope.security.interfaces` -=============================== +================================= + :mod:`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/src/zope/security/interfaces.py b/src/zope/security/interfaces.py index 81addc2..8e8926b 100644 --- a/src/zope/security/interfaces.py +++ b/src/zope/security/interfaces.py @@ -11,7 +11,40 @@ # 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` + """ from zope.interface import Interface, Attribute, implementer @@ -26,31 +59,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 +123,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 +160,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,9 +298,10 @@ 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 """ @@ -210,50 +316,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 +389,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.""" -- cgit v1.2.1 From 34ffae2afa52de6d96ffdfda6409a654f37720e7 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:11:02 -0500 Subject: cross-refs and cleanups for checker and adapter. --- docs/api/adapter.rst | 7 ++- docs/api/checker.rst | 68 ++++++++++++++---------- docs/api/interfaces.rst | 6 +-- docs/api/metaconfigure.rst | 5 ++ docs/conf.py | 1 + docs/index.rst | 16 +++--- src/zope/security/adapter.py | 53 +++++++++++-------- src/zope/security/checker.py | 123 ++++++++++++++++++++++++++++++++++--------- 8 files changed, 188 insertions(+), 91 deletions(-) create mode 100644 docs/api/metaconfigure.rst 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/interfaces.rst b/docs/api/interfaces.rst index 58d7756..5a3bc01 100644 --- a/docs/api/interfaces.rst +++ b/docs/api/interfaces.rst @@ -1,5 +1,5 @@ -================================= - :mod:`zope.security.interfaces` -================================= +========================== + zope.security.interfaces +========================== .. automodule:: zope.security.interfaces 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/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/index.rst b/docs/index.rst index f6418b6..ccd5b84 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,8 +1,9 @@ -:mod:`zope.security` Documentation -================================== +==================================== + ``zope.security`` Documentation +==================================== Narrative Documentation ------------------------ +======================= .. toctree:: :maxdepth: 2 @@ -11,7 +12,7 @@ Narrative Documentation hacking API Reference -------------- +============= .. toctree:: :maxdepth: 2 @@ -26,13 +27,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/src/zope/security/adapter.py b/src/zope/security/adapter.py index f92ac34..e6da570 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 + ` is required, + untrusted adapters need a location in order that the local + authentication mechanism can be inovked 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..7fa2c0c 100644 --- a/src/zope/security/checker.py +++ b/src/zope/security/checker.py @@ -11,17 +11,47 @@ # 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`). -You can set the environment variable ZOPE_WATCH_CHECKERS to get additional -security checker debugging output on the standard error. +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. -Setting ZOPE_WATCH_CHECKERS to 1 will display messages about unauthorized or +Debugging Permissions Problems +============================== + +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. """ import abc import os @@ -156,6 +186,13 @@ 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 @@ -163,8 +200,8 @@ class CheckerPy(object): 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 + :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 +332,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 @@ -330,10 +369,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: @@ -346,7 +390,7 @@ def MultiChecker(specs): 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 +453,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 +507,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 +560,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 +640,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 +670,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) -- cgit v1.2.1 From ed8946b75f216eb956f28f5769eb4af9f31b1eb8 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:18:03 -0500 Subject: Doc cleanup for decorator.rst --- docs/api/decorator.rst | 52 +++++++++++++++++++------------------------- src/zope/security/checker.py | 2 ++ 2 files changed, 24 insertions(+), 30 deletions(-) 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/src/zope/security/checker.py b/src/zope/security/checker.py index 7fa2c0c..5b74242 100644 --- a/src/zope/security/checker.py +++ b/src/zope/security/checker.py @@ -52,6 +52,8 @@ API The special constant that indicates that no permission checking needs to be done. + +.. autofunction:: selectChecker """ import abc import os -- cgit v1.2.1 From e504906ab28fed2decade91f289987dbf7e0d497 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:23:43 -0500 Subject: Doc cross-refs and cleanup for management.rst/py --- docs/api/management.rst | 7 +++---- src/zope/security/management.py | 32 ++++++++++++++++++++------------ 2 files changed, 23 insertions(+), 16 deletions(-) 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/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 -- cgit v1.2.1 From b38c6b4836fe3249710f1a16061576bad8ff8b1a Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:28:23 -0500 Subject: Cross-refs and cleanups for permission.py/rst --- docs/api/permission.rst | 9 ++++++--- src/zope/security/permission.py | 28 ++++++++++++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/docs/api/permission.rst b/docs/api/permission.rst index 268f8f6..dacb0ac 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:: @@ -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/src/zope/security/permission.py b/src/zope/security/permission.py index 9854e3d..e3e8a60 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,7 +41,9 @@ 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 @@ -46,14 +51,16 @@ def checkPermission(context, 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 + `, 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 + `, which is + shortened to 'Public'. Terms are sorted by title except for 'Public', which always appears as the first term. -- cgit v1.2.1 From 1d1aef08f85c99a747f0a18227ac79bc39c168cc Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:34:57 -0500 Subject: Cross-refs and cleanups for proxy.py/rst --- docs/api/protectclass.rst | 7 +++---- docs/api/proxy.rst | 17 +++++++++++++---- src/zope/security/proxy.py | 12 +++++++++++- 3 files changed, 27 insertions(+), 9 deletions(-) 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..733d5df 100644 --- a/docs/api/proxy.rst +++ b/docs/api/proxy.rst @@ -1,14 +1,21 @@ -:mod:`zope.security.proxy` -=============================== +===================== + zope.security.proxy +===================== + +.. currentmodule:: zope.security.proxy .. 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 +42,9 @@ >>> isinstance(ProxyFactory(c), C1) True +.. autoclass:: Proxy +.. autoclass:: ProxyPy .. testcleanup:: diff --git a/src/zope/security/proxy.py b/src/zope/security/proxy.py index 8f6d7c3..55f54f6 100644 --- a/src/zope/security/proxy.py +++ b/src/zope/security/proxy.py @@ -60,6 +60,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 +404,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. """ -- cgit v1.2.1 From f7b02bdaf988f1ba0373bc6c8329dae586281100 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:39:18 -0500 Subject: Cross refs for simplepolicies.py --- docs/api/simplepolicies.rst | 7 +++---- src/zope/security/simplepolicies.py | 11 +++++++++-- 2 files changed, 12 insertions(+), 6 deletions(-) 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/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 `. This means that if there are no participations (and hence no principals), then access is allowed. -- cgit v1.2.1 From d642afc180a0e6eecf732984069b5925e5e5b05f Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:43:49 -0500 Subject: Docs for testing.py --- docs/api/testing.rst | 7 +++---- src/zope/security/interfaces.py | 5 ++++- src/zope/security/testing.py | 25 ++++++++++++++++++++++--- 3 files changed, 29 insertions(+), 8 deletions(-) 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/src/zope/security/interfaces.py b/src/zope/security/interfaces.py index 8e8926b..86d7bff 100644 --- a/src/zope/security/interfaces.py +++ b/src/zope/security/interfaces.py @@ -306,9 +306,12 @@ class IInteraction(Interface): 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): diff --git a/src/zope/security/testing.py b/src/zope/security/testing.py index aae9b53..b6fb8f2 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 + `. + """ 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 + `, 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 -- cgit v1.2.1 From 7befa054c34b6e6d62331d411a8f715f7c51fd42 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 10:54:12 -0500 Subject: ZCML doc cleanup --- docs/api/zcml.rst | 93 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 48 insertions(+), 45 deletions(-) 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 `. - >>> 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 -- cgit v1.2.1 From 04475ed007d4d85249acd490e531c04cabffee30 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 11:00:38 -0500 Subject: Fix some of the outdated info in hacking.rst --- docs/hacking.rst | 126 ++++++++++++++++++++++--------------------------------- 1 file changed, 51 insertions(+), 75 deletions(-) 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. -- cgit v1.2.1 From e8818663a59c5c8b146f599ba87666bf1cd7af75 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 11:28:31 -0500 Subject: Split narr.rst into parts and make the example more correct. It's not doctested so it isn't fully guaranteed to be correct, but it was relying on conpects that don't exist anymore (simpleinteraction, and the ISecurityPolicy having the checkPermission() method). --- docs/example.rst | 227 +++++++++++++++++ docs/index.rst | 8 +- docs/narr.rst | 543 ++++------------------------------------ docs/proxy.rst | 220 ++++++++++++++++ src/zope/security/interfaces.py | 3 + 5 files changed, 508 insertions(+), 493 deletions(-) create mode 100644 docs/example.rst create mode 100644 docs/proxy.rst 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 ` 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 +` 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 ` 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 +- Guido Wesdorp +- Marius Gedminas diff --git a/docs/index.rst b/docs/index.rst index ccd5b84..eb3cbf5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -1,6 +1,6 @@ -==================================== - ``zope.security`` Documentation -==================================== +============================= + zope.security Documentation +============================= Narrative Documentation ======================= @@ -9,6 +9,8 @@ Narrative Documentation :maxdepth: 2 narr + example + proxy hacking API Reference 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 `. Further +specializations include :class:`groups of users ` and +principals that :class:`know what groups they belong to +`. 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 +` that has the special meaning of +"public", i.e., no checking needs to be done. + +There are :class:`permission objects ` that can be +registered as zope.component utilities for validation, introspection, +and producing :func:`lists of available permissions +` 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 ` 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 ` +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 +` and the :class:`global security policy +`. -Stores transient information on the list of participations. - -Participation -~~~~~~~~~~~~~ +:class:`Interaction ` +------------------------------------------------------------ -Stores information about a principal participating in the interaction. +An :class:`interaction ` 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 +` 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 -- Guido Wesdorp -- Marius Gedminas - - -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 +` in the :class:`interaction +`. -- 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 ` 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..f5d8166 --- /dev/null +++ b/docs/proxy.rst @@ -0,0 +1,220 @@ +============================================= + 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. diff --git a/src/zope/security/interfaces.py b/src/zope/security/interfaces.py index 86d7bff..6b50a9f 100644 --- a/src/zope/security/interfaces.py +++ b/src/zope/security/interfaces.py @@ -45,6 +45,9 @@ These can be categorized into a few different groups of related objects. - :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 -- cgit v1.2.1 From 8b93f2bb08c45c18f207f61b6691fd13664f8609 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Thu, 14 Sep 2017 12:17:35 -0500 Subject: Document proxy.__class__ troubles with isinstance/issubclass And suggest workarounds, in a prominent new section about proxy troubles. Link to this from everywhere we talk about proxies. Fixes #26 --- docs/api/proxy.rst | 4 ++ docs/proxy.rst | 97 ++++++++++++++++++++++++++++++++++++++++++++++ src/zope/security/proxy.py | 5 ++- 3 files changed, 105 insertions(+), 1 deletion(-) diff --git a/docs/api/proxy.rst b/docs/api/proxy.rst index 733d5df..0acb043 100644 --- a/docs/api/proxy.rst +++ b/docs/api/proxy.rst @@ -4,6 +4,10 @@ .. 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 diff --git a/docs/proxy.rst b/docs/proxy.rst index f5d8166..20cf3af 100644 --- a/docs/proxy.rst +++ b/docs/proxy.rst @@ -218,3 +218,100 @@ 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. + +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__ + + >>> 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 ` can cause it to arise quite +unexpectedly: + +.. doctest:: + + >>> from collections import Mapping + >>> isinstance(proxy, Mapping) + Traceback (most recent call last): + ... + TypeError: issubclass() arg 1 must be a class + +logging +~~~~~~~ + +Starting with `Python 2.7.7 `_, +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 `_: + +- 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 `: + +.. 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("name", 1, "/path/to/file", 1, + ... "The message %s", (proxy,), None) + + + +Each specific application will have to determine what solution is +correct for its security model. diff --git a/src/zope/security/proxy.py b/src/zope/security/proxy.py index 55f54f6..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 -- cgit v1.2.1 From 502c31eea599977b20c43d8659db09f71ed0cff2 Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 15 Sep 2017 07:43:30 -0500 Subject: Expand the proxy issues section on isinstance. --- docs/proxy.rst | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/docs/proxy.rst b/docs/proxy.rst index 20cf3af..2b01ee0 100644 --- a/docs/proxy.rst +++ b/docs/proxy.rst @@ -227,6 +227,68 @@ 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__ + + >>> type(target) + + >>> proxy = ProxyFactory(target, None) + >>> proxy.__class__ + + >>> 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 ---------------------- @@ -259,11 +321,35 @@ 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 ` 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 ~~~~~~~ -- cgit v1.2.1 From e68d13a28b347ff95b12960ac882c59b504902ed Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 15 Sep 2017 08:01:53 -0500 Subject: Consistent capitalization of ID in docstrings and fields (except one case where it was a i18n messageid). TIL that sphinx.ext.doctest has IGNORE_EXCEPTION_DETAIL set by default. --- docs/api/permission.rst | 2 +- src/zope/security/adapter.py | 2 +- src/zope/security/checker.py | 6 +++--- src/zope/security/interfaces.py | 2 +- src/zope/security/metadirectives.py | 2 +- src/zope/security/permission.py | 4 ++-- src/zope/security/testing.py | 4 ++-- src/zope/security/zcml.py | 8 ++++---- 8 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/api/permission.rst b/docs/api/permission.rst index dacb0ac..107e495 100644 --- a/docs/api/permission.rst +++ b/docs/api/permission.rst @@ -29,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: diff --git a/src/zope/security/adapter.py b/src/zope/security/adapter.py index e6da570..661e49d 100644 --- a/src/zope/security/adapter.py +++ b/src/zope/security/adapter.py @@ -113,7 +113,7 @@ class LocatingUntrustedAdapterFactory(object): other than :const:`zope.Public ` is required, untrusted adapters need a location in order that the local - authentication mechanism can be inovked correctly. + authentication mechanism can be invoked correctly. If the adapter does not provide :class:`zope.location.interfaces.ILocation`, we location proxy it diff --git a/src/zope/security/checker.py b/src/zope/security/checker.py index 5b74242..79975d4 100644 --- a/src/zope/security/checker.py +++ b/src/zope/security/checker.py @@ -201,7 +201,7 @@ class CheckerPy(object): 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, + 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. @@ -357,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. """ @@ -387,7 +387,7 @@ 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. diff --git a/src/zope/security/interfaces.py b/src/zope/security/interfaces.py index 6b50a9f..4c62bdc 100644 --- a/src/zope/security/interfaces.py +++ b/src/zope/security/interfaces.py @@ -433,7 +433,7 @@ class IMemberAwareGroup(IMemberGetterGroup): def setMembers(value): """ - Set members of group to the principal ids in the iterable + Set members of group to the principal IDs in the iterable *value*. """ 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 e3e8a60..b54d285 100644 --- a/src/zope/security/permission.py +++ b/src/zope/security/permission.py @@ -48,11 +48,11 @@ def checkPermission(context, permission_id): 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 permission object utilities. + Get the IDs of all defined permission object utilities. """ for name, _permission in getUtilitiesFor(IPermission, context): if name != zope_Public: diff --git a/src/zope/security/testing.py b/src/zope/security/testing.py index b6fb8f2..bd4b889 100644 --- a/src/zope/security/testing.py +++ b/src/zope/security/testing.py @@ -84,7 +84,7 @@ def addCheckerPublic(): def create_interaction(principal_id, **kw): """ - Create a new interaction for the given principal id, make it the + Create a new interaction for the given principal ID, make it the :func:`current interaction `, and return the :class:`Principal` object. @@ -99,7 +99,7 @@ def create_interaction(principal_id, **kw): def interaction(principal_id, **kw): """ A context manager for running an interaction for the given - principal id. + principal ID. """ if zope.security.management.queryInteraction(): # There already is an interaction. Great. Leave it alone. 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): -- cgit v1.2.1 From e42da5125f7537b0ae7445672b0915d7f388837f Mon Sep 17 00:00:00 2001 From: Jason Madden Date: Fri, 15 Sep 2017 08:45:04 -0500 Subject: Fix manifest.in --- MANIFEST.in | 8 ++++++++ 1 file changed, 8 insertions(+) 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 -- cgit v1.2.1