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 @@ '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.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 @@ def __call__(self, *args): 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 @@ def _customizeProtected(self, adapter, context): 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 @@ def __init__(self, get_permissions, set_permissions=None): 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 @@ def __repr__(self): 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 @@ def getCheckerForInstancesOf(class_): 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 @@ def check_setattr(self, object, name): 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 @@ def check_setattr(self, object, name): # 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)