[Checkins] SVN: zope_secrets/trunk/ - initial import

Jens Vagelpohl jens at dataflake.org
Mon Jan 2 12:13:11 UTC 2012


Log message for revision 123874:
  - initial import

Changed:
  A   zope_secrets/trunk/
  A   zope_secrets/trunk/Makefile
  A   zope_secrets/trunk/bootstrap.py
  A   zope_secrets/trunk/buildout.cfg
  A   zope_secrets/trunk/source/
  A   zope_secrets/trunk/source/.static/
  A   zope_secrets/trunk/source/.templates/
  A   zope_secrets/trunk/source/conf.py
  A   zope_secrets/trunk/source/extensionclass.rst
  A   zope_secrets/trunk/source/hooks.rst
  A   zope_secrets/trunk/source/index.rst
  A   zope_secrets/trunk/source/request.rst
  A   zope_secrets/trunk/source/security.rst
  A   zope_secrets/trunk/source/startup.rst

-=-
Added: zope_secrets/trunk/Makefile
===================================================================
--- zope_secrets/trunk/Makefile	                        (rev 0)
+++ zope_secrets/trunk/Makefile	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,75 @@
+# Makefile for Sphinx documentation
+#
+
+# You can set these variables from the command line.
+SPHINXOPTS    =
+SPHINXBUILD   = bin/sphinx-build
+PAPER         =
+
+# Internal variables.
+PAPEROPT_a4     = -D latex_paper_size=a4
+PAPEROPT_letter = -D latex_paper_size=letter
+ALLSPHINXOPTS   = -d build/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
+
+.PHONY: help clean html web pickle htmlhelp latex changes linkcheck
+
+help:
+	@echo "Please use \`make <target>' where <target> is one of"
+	@echo "  html      to make standalone HTML files"
+	@echo "  pickle    to make pickle files"
+	@echo "  json      to make JSON files"
+	@echo "  htmlhelp  to make HTML files and a HTML help project"
+	@echo "  latex     to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
+	@echo "  changes   to make an overview over all changed/added/deprecated items"
+	@echo "  linkcheck to check all external links for integrity"
+
+clean:
+	-rm -rf build/*
+
+html:
+	mkdir -p build/html build/doctrees
+	$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) build/html
+	@echo
+	@echo "Build finished. The HTML pages are in build/html."
+
+pickle:
+	mkdir -p build/pickle build/doctrees
+	$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) build/pickle
+	@echo
+	@echo "Build finished; now you can process the pickle files."
+
+web: pickle
+
+json:
+	mkdir -p build/json build/doctrees
+	$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) build/json
+	@echo
+	@echo "Build finished; now you can process the JSON files."
+
+htmlhelp:
+	mkdir -p build/htmlhelp build/doctrees
+	$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) build/htmlhelp
+	@echo
+	@echo "Build finished; now you can run HTML Help Workshop with the" \
+	      ".hhp project file in build/htmlhelp."
+
+latex:
+	mkdir -p build/latex build/doctrees
+	$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) build/latex
+	@echo
+	@echo "Build finished; the LaTeX files are in build/latex."
+	@echo "Run \`make all-pdf' or \`make all-ps' in that directory to" \
+	      "run these through (pdf)latex."
+
+changes:
+	mkdir -p build/changes build/doctrees
+	$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) build/changes
+	@echo
+	@echo "The overview file is in build/changes."
+
+linkcheck:
+	mkdir -p build/linkcheck build/doctrees
+	$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) build/linkcheck
+	@echo
+	@echo "Link check complete; look for any errors in the above output " \
+	      "or in build/linkcheck/output.txt."


Property changes on: zope_secrets/trunk/Makefile
___________________________________________________________________
Added: svn:eol-style
   + native

Added: zope_secrets/trunk/bootstrap.py
===================================================================
--- zope_secrets/trunk/bootstrap.py	                        (rev 0)
+++ zope_secrets/trunk/bootstrap.py	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,77 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+
+$Id$
+"""
+
+import os, shutil, sys, tempfile, urllib2
+
+tmpeggs = tempfile.mkdtemp()
+
+is_jython = sys.platform.startswith('java')
+
+try:
+    import pkg_resources
+except ImportError:
+    ez = {}
+    exec urllib2.urlopen('http://peak.telecommunity.com/dist/ez_setup.py'
+                         ).read() in ez
+    ez['use_setuptools'](to_dir=tmpeggs, download_delay=0)
+
+    import pkg_resources
+
+if sys.platform == 'win32':
+    def quote(c):
+        if ' ' in c:
+            return '"%s"' % c # work around spawn lamosity on windows
+        else:
+            return c
+else:
+    def quote (c):
+        return c
+
+cmd = 'from setuptools.command.easy_install import main; main()'
+ws  = pkg_resources.working_set
+
+if is_jython:
+    import subprocess
+    
+    assert subprocess.Popen([sys.executable] + ['-c', quote(cmd), '-mqNxd', 
+           quote(tmpeggs), 'zc.buildout'], 
+           env=dict(os.environ,
+               PYTHONPATH=
+               ws.find(pkg_resources.Requirement.parse('setuptools')).location
+               ),
+           ).wait() == 0
+
+else:
+    assert os.spawnle(
+        os.P_WAIT, sys.executable, quote (sys.executable),
+        '-c', quote (cmd), '-mqNxd', quote (tmpeggs), 'zc.buildout',
+        dict(os.environ,
+            PYTHONPATH=
+            ws.find(pkg_resources.Requirement.parse('setuptools')).location
+            ),
+        ) == 0
+
+ws.add_entry(tmpeggs)
+ws.require('zc.buildout')
+import zc.buildout.buildout
+zc.buildout.buildout.main(sys.argv[1:] + ['bootstrap'])
+shutil.rmtree(tmpeggs)


Property changes on: zope_secrets/trunk/bootstrap.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: zope_secrets/trunk/buildout.cfg
===================================================================
--- zope_secrets/trunk/buildout.cfg	                        (rev 0)
+++ zope_secrets/trunk/buildout.cfg	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,12 @@
+[buildout]
+develop =
+parts =
+    stxpy
+
+[stxpy]
+recipe = zc.recipe.egg
+eggs =
+    Sphinx
+interpreter = stxpy
+scripts =
+    sphinx-build

Added: zope_secrets/trunk/source/conf.py
===================================================================
--- zope_secrets/trunk/source/conf.py	                        (rev 0)
+++ zope_secrets/trunk/source/conf.py	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,190 @@
+# -*- coding: utf-8 -*-
+#
+# Zope Developer Information documentation build configuration file, created by
+# sphinx-quickstart on Mon Feb 23 12:41:00 2009.
+#
+# This file is execfile()d with the current directory set to its containing dir.
+#
+# The contents of this file are pickled, so don't put values in the namespace
+# that aren't pickleable (module imports are okay, they're removed automatically).
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+import sys, os
+
+# If your extensions are in another directory, add it here. If the directory
+# is relative to the documentation root, use os.path.abspath to make it
+# absolute, like shown here.
+#sys.path.append(os.path.abspath('.'))
+
+# General configuration
+# ---------------------
+
+# Add any Sphinx extension module names here, as strings. They can be extensions
+# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = []
+
+# Add any paths that contain templates here, relative to this directory.
+templates_path = ['.templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The encoding of source files.
+#source_encoding = 'utf-8'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'Zope Secrets'
+copyright = u'2012, Zope Developer Community'
+
+# The version info for the project you're documenting, acts as replacement for
+# |version| and |release|, also used in various other places throughout the
+# built documents.
+#
+# The short X.Y version.
+version = '1.0'
+# The full version, including alpha/beta/rc tags.
+release = '1.0'
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+#language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+#today = ''
+# Else, today_fmt is used as the format for a strftime call.
+#today_fmt = '%B %d, %Y'
+
+# List of documents that shouldn't be included in the build.
+#unused_docs = []
+
+# List of directories, relative to source directory, that shouldn't be searched
+# for source files.
+exclude_trees = []
+
+# The reST default role (used for this markup: `text`) to use for all documents.
+#default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+#add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+#add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+#show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+
+# Options for HTML output
+# -----------------------
+
+# The style sheet to use for HTML and HTML Help pages. A file of that name
+# must exist either in Sphinx' static/ path, or in one of the custom paths
+# given in html_static_path.
+html_style = 'default.css'
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+#html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+#html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+#html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+#html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+html_static_path = ['.static']
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+#html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+#html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+#html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+#html_additional_pages = {}
+
+# If false, no module index is generated.
+#html_use_modindex = True
+
+# If false, no index is generated.
+#html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+#html_split_index = False
+
+# If true, the reST sources are included in the HTML build as _sources/<name>.
+#html_copy_source = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+#html_use_opensearch = ''
+
+# If nonempty, this is the file name suffix for HTML files (e.g. ".xhtml").
+#html_file_suffix = ''
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = 'ZopeSecretsdoc'
+
+
+# Options for LaTeX output
+# ------------------------
+
+# The paper size ('letter' or 'a4').
+#latex_paper_size = 'letter'
+
+# The font size ('10pt', '11pt' or '12pt').
+#latex_font_size = '10pt'
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, document class [howto/manual]).
+latex_documents = [
+  ('index', 'ZopeSecrets.tex', ur'Zope Secrets',
+   ur'Zope Developer Community', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+#latex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+#latex_use_parts = False
+
+# Additional stuff for the LaTeX preamble.
+#latex_preamble = ''
+
+# Documents to append as an appendix to all manuals.
+#latex_appendices = []
+
+# If false, no module index is generated.
+#latex_use_modindex = True


Property changes on: zope_secrets/trunk/source/conf.py
___________________________________________________________________
Added: svn:keywords
   + Id
Added: svn:eol-style
   + native

Added: zope_secrets/trunk/source/extensionclass.rst
===================================================================
--- zope_secrets/trunk/source/extensionclass.rst	                        (rev 0)
+++ zope_secrets/trunk/source/extensionclass.rst	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,69 @@
+==================
+The ExtensionClass
+==================
+
+.. admonition:: Description
+
+        What is ``ExtensionClass.Base`` used for?
+
+.. contents :: :local:
+
+What is an ``ExtensionClass``?
+------------------------------
+
+Before Python 2.2 and "new-style" classes, the ``ExtensionClass.ExtensionClass``
+metaclass provided features now found in Python itself. Nowadays, it mainly
+provides three features:
+
+* Support for a class initialiser. Classes deriving from ``ExtensionClass.Base``
+  can define a method ``__class_init__(self)``, which is called when the
+  class is initialised (usually at module import time). Note that ``self``
+  here is the class object, not an instance of the class.
+* Ensuring that any class that has ``ExtensionClass`` as a ``__metaclass__``
+  implicitly get ``ExtensionClass.Base`` as a base class.
+* Providing an ``inheritedAttribute`` method, which acts a lot like ``super()``
+  and is hence superfluous except for in legacy code.
+
+The base class ``ExtensionClass.Base`` provides the ``__of__`` protocol that is
+used by acquisition. It is similar to the ``__get__`` hook used in Python
+descriptors, except that ``__of__`` is called when an implementor is retrieved
+from an instance as well as from a class. Here is an example::
+
+.. code-block:: python
+
+  >>> from ExtensionClass import Base
+  >>> class Container(Base):
+  ...     pass
+
+  >>> class Item(Base):
+  ...     def __init__(self):
+  ...         self.visited = []
+  ...     def __of__(self, parent):
+  ...         self.visited.append(parent)
+  ...         return self
+
+  >>> container = Container()
+  >>> item = Item()
+  >>> item.visited
+  []
+  >>> container.item1 = item
+  >>> item.visited
+  []
+  >>> container.item1
+  <__main__.O object at 0x10cc0ddd0>
+  >>> item.visited
+  [<__main__.C object at 0x10cc0dc90>]
+
+  >>> container.item1 # again
+  <__main__.O object at 0x10cc0ddd0>
+  >>> item.visited
+  [<__main__.C object at 0x10cc0dc90>, <__main__.C object at 0x10cc0dc90>]
+
+There is probably little reason to use ``ExtensionClass.Base`` in new code,
+though when deriving from ``Acquisition.Implicit`` or ``Acquisition.Explicit``,
+it will be included as a base class of those classes.
+
+How does acquisition work?
+--------------------------
+
+Black magic.
\ No newline at end of file

Added: zope_secrets/trunk/source/hooks.rst
===================================================================
--- zope_secrets/trunk/source/hooks.rst	                        (rev 0)
+++ zope_secrets/trunk/source/hooks.rst	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,90 @@
+==================
+Zope's many hooks
+==================
+
+.. admonition:: Description
+
+        What hooks does Zope provide for application code?
+
+Zope provides many different hooks that can be used to execute code at various
+times during its lifecycle. The most important ones are outlined below.
+
+.. contents :: :local:
+
+Process lifecycle
+-----------------
+
+``zope.processlifetime`` defines three events:
+
+* ``IDatabaseOpened``, notified when the main ZODB has been opened, but before
+  the root application object is set.
+* ``IDatabaseOpenedWithRoot``, notified later in the startup cycle, when the
+  application root has been set and initalised.
+* ``IProcessStarting``, notified when the Zope startup process has completed,
+  but before the Zope server runs (and so can listen to requests).
+
+ZODB connection lifecycle
+-------------------------
+
+The list ``App.ZApplication.connection_open_hooks`` can be used to hold
+functions that are called with a ZODB connection as their sole argument just
+after traversal over the ``ZApplicationWrapper`` as it opens a ZODB connection
+for the request.
+
+The ZODB transaction provides two methods to register hooks -
+``addBeforeCommitHook()`` and ``addAfterCommitHook()``. These can be passed
+functions and a (static) set of arguments and will be called just before, and
+just after, a transaction is committed. The hook function must take at least one
+argument, a boolean indicating whether the transaction succeeded.
+
+Use ``transaction.get()`` to get hold of the transaction object. See
+``transaction.interfaces.ITransaction`` for more details.
+
+Request lifecycle
+-----------------
+
+Request-scoped items may be held from garbage collection using
+``request._hold()``. If applicable, the item held can implement ``__del__()``,
+which will be called when the request is destroyed.
+
+The event ``zope.publisher.events.EndRequestEvent`` is triggered at the end
+of an event, just before any held items are cleared.
+
+Publication
+-----------
+
+The publisher notifies a number of events, which can be used to hook into
+various stages of the publication process. These are all defined in the module
+``ZPublisher.pubevents``.
+
+When an exception is raised, a view registered for the exception type as
+context (and a generic request) named ``index.html`` will be rendered as an
+error message, if it exists.
+
+Traversal
+---------
+
+If an object has a method ``__bobo_traverse__(self, request, name)``, this will
+be used during traversal in lieu of attribute or item access. It is expected to
+return the next item to traverse to given the path segment ``name``. A more
+modern approach is to register an adapter to ``IPublishTraverse`` although this
+only applies to publication (URL) traversal, not path traversal.
+
+The method ``__before_publishing_traverse__(self, object, request)`` can be
+implemented to be notified when traversal first finds an object. Implemented on
+a class, the ``self`` and ``object`` parameters will be the same.
+
+See also the ``SiteAccess`` package, which implements a through-the-web
+manageable, generic multi-hook to let any callable be invoked before access
+through an "AccessRule".
+
+The event ``zope.traversing.interfaces.IBeforeTraverseEvent`` is notified when
+traversing over something that is a local component site, e.g. the Plone site
+root.
+
+The ``__browser_default__`` method can be implemented to specify a "default
+page" (akin to an ``index.html`` in a folder). A more modern way to do this is
+to register an adapter to ``IBrowserPublisher``.
+
+An adapter to ``ITraversable`` can be used to implement namespace traversal
+(``.../++<namespace>++name/...``). See above for further details.
\ No newline at end of file

Added: zope_secrets/trunk/source/index.rst
===================================================================
--- zope_secrets/trunk/source/index.rst	                        (rev 0)
+++ zope_secrets/trunk/source/index.rst	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,21 @@
+============
+Zope secrets
+============
+
+This documentation will attempt to explain some of Zope's internals. It may be
+useful for debugging purposes, or simply to better understand how Zope works.
+
+The guide pertains to Zope 2.13.
+
+If you only want to know how to *use* the APIs and features described below,
+you are probably better served reading the
+`Zope Developer's Guide <http://docs.zope.org/zope2/zdgbook/>`_.
+
+.. toctree::
+    :maxdepth: 2
+
+    startup
+    request
+    security
+    extensionclass
+    hooks

Added: zope_secrets/trunk/source/request.rst
===================================================================
--- zope_secrets/trunk/source/request.rst	                        (rev 0)
+++ zope_secrets/trunk/source/request.rst	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,486 @@
+======================
+Requests and traversal
+======================
+
+.. admonition:: Description
+
+        How does Zope handle requests and translate paths to
+        published objects?
+
+.. contents :: :local:
+
+What happens when a request is received?
+----------------------------------------
+
+A request is recieved either via a WSGI pipeline or the Medusa web server. Using
+Medusa, it first hits ``handle_request()`` in the ``zhttp_handler`` used by
+``zhttp_server``, which consumes the request until it has enough to act on it,
+at which point ``continue_request()`` is called. This constructs a
+``ZPublisher.HTTPRequest`` from the Medusa ``http_request`` environment and
+prepares a ``ZServerHTTPResponse``, a subclass of ``ZPublisher``'s
+``HTTPResponse``.
+
+The actual request is delegated to a threadpool. In a non-WSGI setup, this
+is managed by ``ZServer.PubCore.ZRendezvous.ZRendevous`` (note the typo in the
+module name!). This keeps track of the requests and (skeletal) responses to
+be processed, and passes them to an instance of a
+``ZServer.PubCore.ZServerPublisher`` for handling. ``ZRendevous`` also deals
+with thread locking.
+
+The ``ZServerPublisher`` will call either ``ZPublisher.publish_module`` or
+``ZPublisher.WSGIPublisher.publish_module``, depending on the deployment mode,
+with the request and the response. The non-WSGI version also takes a module
+name to publish, which is ``Zope2``. This is a relic of the Bobo publisher,
+which could publish other modules with a ``bobo_application`` variable set
+(recall that this variable was set in the startup phase described above).
+
+The remainder of this section will describe the non-WSGI publisher. The WSGI
+publisher performs the same actions, but deals in WSGI environs and response
+body iterators.
+
+There are two versions of ``publish_module``, one with profiling and one
+without. ``publish_module_standard`` (without profiling) performs the following
+actions:
+
+* Set the default ZTK skin on the request, by adapting the request to
+  ``IDefaultSkin``.
+* Call ``publish()``, which does the real publication.
+* Handle errors.
+* Write the response body to ``stdout``, which is wired up to be the HTTP
+  response stream.
+
+The more interesting function is ``publish()``. This starts by calling
+``get_module_info()`` to get the information about the published module
+(which, recall, is almost always going to be ``Zope2``). The results are
+cached, so this will only do its work once:
+
+.. code-block:: python
+
+    (bobo_before, bobo_after, object, realm, debug_mode, err_hook,
+     validated_hook, transactions_manager)= get_module_info(module_name)
+
+The returned variables are:
+
+* ``bobo_before``, set via a module level variable ``__bobo_before__``. This is
+  a callable that will be invoked immediately before publication.
+* ``bobo_after``, set via a module level variable ``__bobo_after__``. This is a
+  callable that will be invoked immediately after publication.
+* ``object`` to publish, which defaults to the module itself, but can be
+  set via the module-level variable ``bobo_application`` (or ``web_objects``)
+* ``realm``, set via the module level variable ``__bobo_realm__``, or a global
+  default which can be set the ``ZConfig`` configuration file.
+* ``debug_mode``, a boolean set using the module level variable
+  ``__bobo_debug_mode__``.
+* ``err_hook``, set via the module level variable ``zpublisher_exception_hook``.
+  This is used to handle error responses (more below).
+* ``validated_hook``, set via the module level variable
+  ``zpublisher_validated_hook``. This is used to initialise a security manager
+  once authentication and authorisation have taken place (more below).
+* ``transactions_manager``, set via the module level variable
+  ``zpublisher_transactions_manager``, but defaulting to the
+  ``DefaultTransactionsManager`` which uses the ``transaction`` API to manage
+  transactions.
+
+The publisher then performs the following steps:
+
+* Notify the ``ZPublisher.pubevents.PubStart`` event.
+* Create a new ``zope.security`` interaction.
+* Call ``processInputs()`` on the request to process request parameters and
+  the request body so that the Zope request object works as advertised.
+* If the request contains a key ``SUBMIT`` with the value ``cancel`` and
+  a key ``cancel_action`` with a path, a ``Redirect`` exception is raised,
+  which will cause an HTTP 302 redirect to be raised.
+* Set ``debug_mode`` and ``realm`` on the response, as returned by
+  ``get_module_info()``.
+* If ``bobo_before()`` is set, it is called with no arguments.
+* Set the inital value for ``request['PARENTS']`` to be the published
+  object. This will be the ``ZApplicationWrapper`` set during the startup
+  phase.
+* Begin a transaction using the ``transactions_manager``.
+* Traverse to the actual object being published (e.g. a view) by calling
+  ``object=request.traverse(path, validated_hook=validated_hook)``, where
+  ``path`` is ``request['PATH_INFO']``. More on traversal below.
+* Notify the ``ZPublisher.pubevents.PubAfterTraversal`` event.
+* Note the path and authenticated user in the transaction.
+* Call the object being pusblished using ``mapply()``:
+
+  .. code-block:: python
+
+        result=mapply(object, request.args, request,
+                      call_object,1,
+                      missing_name,
+                      dont_publish_class,
+                      request, bind=1)
+
+  The ``ZPublisher.mapply.mapply()`` method is somewhat complicated, but in
+  essence all it does is to call either a published method, or a published
+  instance with a ``__call__()`` method.
+
+  ``request.args`` can contain positional arguments supplied in an XMLRPC call,
+  but is usually empty. The ``request`` is passed to act as a dictionary of
+  keyword arugments, which allows request parameters to be turned into
+  method parameters to a published method.
+
+  The other parameters are about policy - we call any object (e.g. a method or
+  object with a ``__call__`` method) to resolve it, but we don't publish class
+  objects (which would in effect instantiate them). We do allow binding of
+  ``self`` for methods on objects, and we pass the ``request`` as context for
+  debugging.
+* Set the result of the ``mapply()`` call as the response body. As a marker,
+  the response object itself can be returned from the callable that ``mapply()``
+  invokes to bypass this behaviour, i.e. if the published object set the
+  response body itself.
+* Notify the ``ZPublisher.pubevents.PubBeforeCommit`` event.
+* Commit the transaction using the ``transactions_manager``.
+* End the ``zope.security`` interaction
+* Notify the ``ZPublisher.pubevents.PubSuccess`` event.
+* Return the response object, which is then used by the ZServer to write to
+  stdout.
+
+If an exception happens during this process, the ``err_hook`` is called. This
+is allowed to raise a ``Retry`` exception. Regardless, the event
+``ZPublisher.pubevents.PubBeforeAbort`` is notified before the transaction is
+aborted, and then ``ZPublisher.pubevents.PubFailure`` is raised after the
+``zope.security`` interaction is ended.
+
+If the request supports retry, it will be retried by cloning it an calling
+``publish`` recrusively. All HTTP requests support retry, but only up to a limit
+of ``retry_max_count``, which by default is 3. Retry is mainly used to retry in
+the case of write conflict errors.
+
+If there is no error hook installed, a simple abort is encountered, with no
+retry.
+
+The default error hook is an instance of
+``Zope2.startup.ZPublisherExceptionHook``. This handles exceptions by performing
+the following checks:
+
+* ``SystemExit`` or ``Redirect`` exceptions are re-raised.
+* A ``ConflictError``, which indicates a write-conflict in the ZODB, is turned
+  into a ``Retry`` exception so that request can be retried.
+* Other exception are stored in the ``__error_log__`` acquired from the
+  published object, if possible.
+* If a view named ``index.html`` is registered with the exception type as its
+  context, this is resolved and returned as the response.
+* If the published object or any of its acquisition parents have a method
+  ``raise_standardErrorMessage()``, this will be called to create an error
+  message instead of using the view approach. This is called with a first
+  argument of whichever object in the acquisition chain has an attribute
+  ``standard_error_message``, as well as the request and traceback information.
+
+When handling an exception by returning an error message, the
+``ZPublisherExceptionHook`` will call ``response.setStatus()`` with the
+exception type (class) as an argument. The *name* of the exception class is
+then used to look up the status code in the ``status_reasons`` dictionary in
+``ZPublisher.HTTPResponse``. Hence, raising an exception called ``NotFound``
+will automatically set the response code to 404.
+
+How does publication traversal work?
+------------------------------------
+
+Traversal is the process during which the path elements of a URL are resolved
+to an actual object to publish (there is also *path traversal*, used in TAL
+expressions in page templates, which is similar, but implemented differently -
+see below).
+
+Traversal is invoked during object publication, which calls
+``request.traverse()`` with the path from the request (the ``PATH_INFO`` CGI
+variable). This method is inordinately complicated, mostly because it caters for
+a lot of edge cases. The basic idea is pretty simple, though: each path element
+represents an item to traverse to from the preceding object (its parent).
+Traversal can mean dict-like access (``__getitem__``), attribute-like access
+(``__getattr__``), or one of a number of different hooks for overiding or
+extending traversal. Once the final element on the path is found, the user's
+access to it is validated, before it is returned to be passed to ``mapply()``.
+
+Here are the gory details:
+
+* Clean up the path up by stripping leading and trailing slashes, explicitly
+  disallowing access to things like ``REQUEST``, ``aq_base`` and ``aq_self``,
+  and resolving ``.`` or ``..`` elements as in filesystem paths.
+* Check if the top-level object (the application root) has a
+  ``__bobo_traverse__`` method (it almost certainly will - as shown above, there
+  is a wrapper around the application root that implements this method to open
+  and close the ZODB connection upon traversal). If so, call it to obtain a new
+  top level object (which will be the real Zope application root in the ZODB).
+* Aquisition-wrap the top-level object in a ``RequestContainer``. This is the
+  fake root object that makes it possible to acquire the attribute ``REQUEST``
+  from any traversed-to context.
+* Record the request variable ``ACTUAL_URL``, which is the inbound URL plus
+  the original path. Hence, this variable provides access to the URL as the
+  user saw it.
+* Set up (and later, pop from) the request variable
+  ``TraversalRequestNameStack``. This is a stack of path elements still to be
+  processed. Traversal hooks sometimes use this to look ahead at the path
+  elements that have not been traversed to and, in some cases, modify the
+  stack to trick traversal into going somewhere other than what the inbound
+  path specified.
+* In a loop, process the traversal name stack:
+
+  * Check if the current object (initially the application root) has a method
+    ``__before_publishing_traverse__``. If so, call it with the request as an
+    argument. This hook is used by many parts of Zope, CMF and Plone to support
+    things like content object method aliases, setting the CMF skin from the
+    request, or making the ``portal_factory`` tool work. This method cannot
+    easily change the traversal path, except by modifying
+    ``request['TraversalRequestNameStack']``.
+  * If there are more elements in the path, pop the next element.
+  * Append this to the variable ``request['URL']``, which contains the traversal
+    URL. Various traversal tricks may mean this is not quite the same as what
+    the user sees in their address bar, but it should be valid, traversable URL.
+  * Attempt to traverse to the next object using the name popped from the path
+    stack. This takes place in the ``traverseName()`` method of the request:
+
+    * If the name starts with a ``+`` or an ``@``, parse it as a traversal
+      namespace. (A name starting with an ``@`` is taken as a shorthand for
+      ``++view++<name>``, i.e. an entry in the ``++view++`` traversal namespace.
+      Other namespaces include ``++skin++`` and ``++etc++``.) If a traversal
+      namesapce is found, attempt to look up an adapter from the current
+      traversal object and the request to
+      ``zope.traversing.interfaces.ITraversable`` with a name matching the
+      traversal namespace (e.g. ``view``). Then call its ``traverse()`` method
+      with the name of the next entry on the traversal stack as an argument.
+      This is expected to return an object to traverse to next. If this
+      succeeds, acquisition-wrap the returned object in the parent object.
+
+      **Note:** As this implies, objects returned from the ``traverse()`` method
+      of an ``ITraversable`` adapter are *not* expected to be
+      acquisition-wrapped. This is in contrast to objects returned by
+      ``__bobo_traverse__()``, ``__getitem__()``, ``__getattr__()``, or a custom
+      ``IPublishTraverse`` adapter (see below), which *are* expected to be
+      wrapped.
+    * If there is no namespace traversal adapter, find an ``IPublishTraverse``
+      object in one of three places: If the current traversal object implements
+      it directly, use that; if there is an adapter from the current object
+      and the request to ``IPublishTraverse``, use that; or, fall back to the
+      ``DefaultPublishTraverse`` implementation found in
+      ``ZPublisher.BaseRequest``. Then call the ``publishTraverse()`` method
+      to find an object to traverse to and return that (without
+      acquisition-wrapping it).
+
+      Implementing ``IPublishTraverse`` is a common way to allow further
+      traversal from a view, with paths like ``...../@@foo/some/path``, where
+      the ``@@foo`` view either implements or is adaptable to
+      ``IPublishTraverse``.
+
+      ``DefaultPublishTraverse`` is used in most cases, either directly or as a
+      fallback from custom implementations. It uses the following semantics:
+
+      * If the name starts with an underscore, raise a ``Forbidden`` exception
+      * If the object has a ``__bobo_traverse__`` method, call it with the
+        request and the name of the next entry on the traversal stack as
+        arguments. It may return either an object, or a tuple of objects.
+        In the latter case, amend request parents list as if traversal had
+        happened over all the elements in the tuple except the last one, and
+        treat that as the next object.
+      * If the ``__bobo_traverse__`` call fails by raising an
+        ``AttributeError``, ``KeyError`` or ``NotFound`` exception, attempt
+        to look up a view with the traversal name (which would have been given
+        without the explicit ``@@`` prefix). If this succeeds, set the status
+        code to 200 (the preceding failure may have set it to 404),
+        acquisition-wrap the view if applicable, and return it.
+      * If there was no ``__bobo_traverse__``, or if it raised the special
+        exception ``ZPublisher.interfaces.UseTraversalDefault``, try the
+        following:
+
+        * Attempt to look up the name as an attribute of the current object,
+          using ``aq_base`` (i.e. explicitly not acquiring from parents of
+          the current object). If this succeeds, return the attribute, which
+          is expected to be acquisition-wrapped if applicable (i.e. the
+          parent object extends ``Acquisition.Implicit`` or
+          ``Acquisition.Explicit``).
+        * Next, try to look up a view using the same semantics as above
+        * Next, try ``getattr()`` without the ``aq_base`` check, i.e.
+          allowing acquired attributes.
+        * Next, try ``__getitem__()`` (dict-like) access.
+        * If that fails, raise a ``KeyError`` to indicate the object could
+          not be found (this is later turned into a 404 response).
+
+      * If we now have a sub-object, check that it has a docstring. If it
+        does not, raise a ``Forbidden`` exception.
+
+        The requirement for a docstring is an ancient and primitive security
+        restriction, since Zope can be used to publish all kinds of Python
+        objects. It is mostly a nuisance these days, but note that views and
+        custom ``ITraversable`` and ``IPublishTraverse`` traversal do not have
+        this restriction.
+      * Next, raise a ``Forbidden`` exception if traversal resolved a
+        primitive or built-in list, tuple, set or dict - these are not
+        directly traversable.
+      * Finally, return the object
+  * If a ``KeyError``, ``AttributeError`` or ``NotFound`` exception is raised
+    during name resolution, return a 404 response by raising an exception.
+    Similarly, if a ``Forbidden`` exception is raised, set and return a 403
+    response.
+  * Once the end of the path is reached, we have the most specific item
+    mentioned in the (possibly mutated) path. However, this may choose to
+    delegate to another object (usually a subobject) through a mechanism known
+    as "browser default", which is similar to the way web servers often serve
+    an ``index.html`` file by default when traversing to a folder.
+
+    A browser publisher is described by the interface ``IBrowserPublisher``,
+    which is a sub-interface of ``IPublishTraverse`` and is implemented by the
+    ``DefaultPublishTraverse`` class. Again, the ``IBrowserPublisher`` for the
+    traversed-to object is found in one of three ways: the object may implement
+    it itself; or it may be adaptable, with the request, to this interface; or
+    the fallback ``DefaultPublishTraverse`` may be used. The
+    ``browserDefault()`` method on the ``IBrowserPublisher`` is then called
+    with the request as an argument.
+
+    The return value from ``browserDefault()`` is a tuple of a parent object
+    (usually the most recently traversed-to object, i.e. ``self.context`` in the
+    adapter) and a tuple of further names to traverse to from this parent.
+
+    The default implementation in ``DefaultPublishTraverse`` does this:
+
+    * If the object has a method ``__browser_default__()``, delegate to this.
+    * If an ``IDefaultViewName`` has been registered for the context in ZCML,
+      look up and use this. This is deprecated, however.
+    * Otherwise, return ``self.context, ()``, i.e. no further traversal
+      required.
+
+  * If a further path is returned and it has more than one element, add its
+    elements to the ``TraversalRequestNameStack`` and continue traversal as if
+    these elements had been part of the original path all along.
+  * If there is only one element in the further path returned by
+    ``browserDefault()``, use this as the next entry name and continue traversal
+    to this.
+  * If no further path is used, fall back on the default method name
+    ``index_html()`` (applicable for HTTP ``GET`` and ``POST`` requests - there
+    is special handling of other HTTP verbs for WebDAV that we won't go into
+    here) and continue traversal to this.
+  * If there is no ``index_html()`` method, use the traversed-to object itself
+    as the final entry, so break out of the traversal loop. We always end up
+    here eventually: if the browser default element or ``index_html()`` method
+    is the last item we traverse to, eventually we reach something publishable.
+
+    This object will most likely be called (through ``mapply()``), so we ensure
+    the roles used in security checks are obtained from the ``__call__()``
+    method the traversed-to object (note: function and method objects also have
+    a ``__call__()`` in Python).
+* Once we have reached the end of the traversal stack (phew!), we make sure
+  the ``parents`` list is in the right order (it is built in reverse order),
+  even if there was a failure. Hence, ``request['PARENTS']`` is always a useful
+  indicator of what objects have been traversed over, with the last item being
+  the special request container and the penultimate item being the application
+  root.
+* We then set ``request['PUBLISHED']`` to be the published callable. Note that
+  this is usually a view or page template, though for content types like
+  ``File`` or ``Image`` it is the ``index_html()`` method of the content object
+  itself.
+* Next, we validate that the current user has sufficient permissions to call
+  the published object. If not, a 403 response is returned by calling
+  ``response.unauthorized()``.
+
+  The authentication works as follows:
+
+  * The roles required to access the traversed-to object are fetched by calling
+    ``getRoles()``, first on the application root, and, if applicable, on the
+    ``__call__()`` method of the traversed-to object.
+  * A user folder (i.e. ``acl_users``) is obtained by looking for the special
+    attribute ``__allow_groups__`` on the published object or one of its
+    parents. This attribute is set by user folders on their parent container
+    when they are added.
+  * The ``validate()`` method of the user folder is called (there is a fallback
+    called ``old_validate()``, used if there is no user folder, but that should
+    never happen in a modern Zope installation). This either returns a user
+    object or ``None``, if the user is not found in this user folder, or there
+    is a user, but the user cannot be auhtorised according to this user folder.
+  * If ``None`` is returned, the search continues up the list of traversal
+    parents until a suitable user folder is found. If no such user folder is
+    found, an ``Unauthorized`` exception is raised, unless there are no security
+    declarations on the context.
+  * If a user with permissions is found, and the ``validated_hook`` is set
+    (found via ``get_module_info()`` as described above), it is called with the
+    request and user as arguments. The standard ``validated_hook`` calls
+    ``newSecurityManager()`` with the user, which sets the security context for
+    the remainder of the request.
+  * The user is then saved in the request variable ``AUTHENTICATED_USER``. The
+    true traversal path is saved in the request variable
+    ``AUTHENTICATION_PATH``.
+
+* Finally, if any post-traverse functions have been registered (by using the
+  ``post_traverse()`` method of the request to register functions and optional
+  static arguments), they are called in the order they were registered. If any
+  post-traverse function returns a value other than ``None``, no further
+  post-traverse functions are called, and the return value is used as the return
+  value of the ``traverse()`` function, discarding the actual object that was
+  traversed to and security check.
+
+How does path traversal work?
+-----------------------------
+
+Path traversal is invoked when using path expressions in page templates or
+action expressions (e.g. ``context/Title``). It may be invoked explicitly in
+code using the methods ``restrictedTraverse()`` (which performs security checks)
+or ``unrestrictedTraverse()`` (which does not), defined in
+``OFS.Traversable.Traversable`` and mixed into most persistent items in Zope.
+This is semantically similar to publication (URL) traversal as described above,
+but is not identical - see below.
+
+All the logic is in the ``unrestrictedTraverse()`` method, which takes an
+optional argument ``restricted`` that is set to ``True`` when called via
+``restrictedTraverse()``. It takes a ``path`` string or element list as an
+argument, and optionally a default to return if traversal fails. If no default
+is specified, an exception will be raised if traversal fails. This may either be
+an ``AttributeError``, ``KeyError`` or ``NotFound`` exception, depending on what
+type of traversal failed.
+
+If ``restricted`` is ``True``, ``unrestrictedTraverse()`` will perform a
+security check using ``getSecurityManager().validate()`` for every step of
+traveral. This is different to URL traversal, which only validates at the end
+of traversal.
+
+The implementation does the following:
+
+* Strip any trailing slash from the ``path``
+* If the path starts with a slash, begin traversal from the physical application
+  root. Otherwise, start from ``self``. If performing restricted traversal from,
+  the application root, validate access to it.
+* For each slash-separated name element of the path:
+
+  * If the name starts with an underscore, raise a ``zExceptions.NotFound``
+    exception - traveral to names starting with an underscore is never allowed.
+  * If the name is ``..``, get the acqusition parent of the current traversal
+    object and continue traversal from here after validating access if
+    applicable.
+  * Otherwise, if the name starts with a ``+`` or ``@``, perform traversal
+    namespace lookup as described for publication traversal above. If this
+    throws a ``LocationError``, fail with an ``AttributeError``. If it suceeds,
+    acquisition-wrap the result if possible and validate access to it if
+    applicable before continuing traversal from this object.
+  * Otherwise, if the object has a ``__bobo_traverse__()`` hook, invoke it to
+    get the next object to traverse to. If this succeeds, validate access to the
+    result if applicable, taking into account that it could be a method or
+    non-security aware object, and that it may or may not be
+    acquisition-wrapped. Then continue traversal from this object.
+  * If there was no ``__bobo_traverse__()``, or if it returned or raised the
+    sentinel ``ZPublisher.interfaces.UseTraversalDefault``, attempt to obtain a
+    non-acquired attribute of the current object with the applicable name. If
+    one is found, continue traversal from this. If security checking is being
+    performed, use ``guarded_getattr()`` from ``AccessControl.ZopeGuards`` to
+    get the attribute, which may raise ``Unauthorized``. (This is
+    the special ``getattr()`` that is also used for all attribute access by
+    untrusted Python code.) Otherwise, use standard ``getattr()``.
+  * Otherwise, attempt dict-like (``__getitem__``) access and validate the
+    result if applicable before continuing traversal from this object.
+  * If any of the above failed with an ``AttributeError``, ``NotFound`` or
+    ``KeyError``, attempt to look up a view on the current traversal object with
+    the given name. If one is found, acquisition-wrap it if possible and
+    validate access if applicable, before continuing traversal from the view
+    instance.
+  * If there is no view, but there was a ``__bobo_traverse__``, fail by re-
+    raising the original exception. The logic behind this is that if there is a
+    ``__bobo_traverse__()``, we should not attempt to acquire attributes.
+  * Assuming we still don't have a value and there was no
+    ``__bobo_traverse__()``, attempt to acquire an attribute, using either
+    ``getattr()`` or ``guarded_getattr()`` depending on whether security checks
+    are being made and continue traversal from the result if this suceeds.
+* If we reach the end of the path, return the most recently traversed-to object.
+* If an exception of any kind (other than a ``ConflictError``) is thrown and a
+  ``default`` was passed in, return this rather than letting the exception
+  bubble up to the caller.
+
+Note: This logic does *not* check for the publication/request-orientated
+``IPublishTraverse`` or ``IBrowserPublisher`` hooks, although they *do* allow
+traversal to a view (e.g. ``context.restrictedTraverse('@@some-view')``).

Added: zope_secrets/trunk/source/security.rst
===================================================================
--- zope_secrets/trunk/source/security.rst	                        (rev 0)
+++ zope_secrets/trunk/source/security.rst	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,593 @@
+========
+Security
+========
+
+.. admonition:: Description
+
+        How does Zope handle permissions, roles and users?
+
+Much of Zope security is implemented in C, for speed, but there is a Python
+implementation in ``AccessControl.ImplPython``, which can be enabled by setting
+``security-policy-implementation python`` in ``zope.conf``.
+
+Note: We will not discuss RestrictedPython, used to apply security restrictions
+to through-the-web python scripts and page templates, here.
+
+.. contents :: :local:
+
+Declaring object roles and attribute permissions
+------------------------------------------------
+
+The permissions required to access a given attribute are stored on classes and
+modules in a variable called ``__ac_permissions__``. This contains a tuple of
+tuples that map a permission name to a list of attributes (e.g. methods)
+protected by that permission, e.g.:
+
+.. code-block:: python
+
+    __ac_permissions__=(
+
+    ('View management screens',
+     ['manage','manage_menu','manage_main','manage_copyright',
+      'manage_tabs','manage_propertiesForm','manage_UndoForm']),
+    ('Undo changes',       ['manage_undo_transactions']),
+    ('Change permissions', ['manage_access']),
+    ('Add objects',        ['manage_addObject']),
+    ('Delete objects',     ['manage_delObjects']),
+    ('Add properties',     ['manage_addProperty']),
+    ('Change properties',  ['manage_editProperties']),
+    ('Delete properties',  ['manage_delProperties']),
+    ('Default permission', ['']),
+    )
+
+The roles reuqired to access an object (e.g. a content object), are stored
+in a class or instance variable ``__roles__``. This may contain a tuple or list
+of role names, an ``AccessControl.PermissionRole.PermissionRole`` object, or one
+of the following special variables:
+
+``AccessControl.SecurityInfo.ACCESS_NONE``
+  Inaccessible from any context
+``AccessControl.SecurityInfo.ACCESS_PRIVATE``
+  Accessible only from Python code
+``AccessControl.SecurityInfo.ACCESS_PUBLIC``
+  Accessible from restricted Python code and publishable through the web
+  (provided the object has a docstring)
+
+For attributes (including methods), the roles are stored on the parent class in
+a variable called ``<name>__roles__``, where ``<name>`` is the attribute name.
+Again, the special variables ``ACCESS_NONE``, ``ACCESS_PRIVATE`` and
+``ACCESS_PUBLIC`` can be used.
+
+These variables are rarely set manually. Instead, declarative security info
+is typically used. For example:
+
+.. code-block:: python
+
+  from App.class_init import InitializeClass
+  from AccessControl.SecurityInfo import ClassSecurityInfo
+  from OFS.SimpleItem import Item
+
+  class SomeClass(Item):
+
+    ...
+
+    security = ClassSecurityInfo()
+    security.declareObjectPublic() # like __roles__ = ACCESS_PUBLIC
+
+    security.declareProtected('Some permission, 'someMethod')
+    def someMethod(self):
+      ...
+
+    InitializeClass(SomeClass)
+
+There is also ``security.declareObjectProtected(<permission>)``,
+``security.declareObjectPrivate()``, ``security.declarePrivate(<attribute>)``
+and ``security.declarePublic(attribute)``, which do as their names suggest to
+make an object or attribute protected, private or public.
+
+Attribute security can be set in ZCML using the ``<class />`` directive with
+one or more ``<require />`` sub-directives:
+
+.. code-block:: xml
+
+  <class class=".someclass.SomeClass">
+    <require
+      permission="some.permission"
+      attributes="someMethod"
+      />
+  </class>
+
+Behind the scenes, this simply creates a ``ClassSecurityInfo`` and invokes it
+on the attributes listed as applicable. This will also call ``InitializeClass``
+on the given class.
+
+Note that the ``<require />`` directive, in common with all ZCML directives,
+uses ZTK-style permission names, not Zope 2-style permission strings. A ZTK
+permission is a named utility providing
+``zope.security.interfaces.IPermission``, with an ``id`` that is the short
+(usually dotted) name that is also the utility name, and a ``title`` that
+matches the Zope 2 name. New permissions can be registered using the
+``<permission />`` directive:
+
+.. code-block:: xml
+
+  <permission
+    id="some.permission"
+    title="Some permission"
+    />
+
+Zope 2-style permission names spring into existence whenever used in a security
+declaration, which makes them susceptibly to typos (ZTK-style ``IPermission``
+utilities must be explicitly registered before they can be used).
+
+Permissions are also represented by "mangled" permission names, which simply
+turn the arbitrary string name of a permission into a valid Python identifier.
+For example, the permission ``"Access contents information"`` becomes
+``_Access_contents_information_Permission``. The mangling is done by the
+function ``AccessControl.Permission.pname``.
+
+``ClassSecurityInfo`` does little except record information until the
+``InitializeClass()`` call is made with the class as an arugment. This will:
+
+* Loop over all attributes and assign a ``__name__`` attribute to the value of
+  any attribute in the class's ``__dict__`` that has the ``_need__name__``
+  marker set (this is used by through-the-web DTML and Zope Page Template
+  objects that may not have a name until they are assigned to their parent).
+* Look for any function with the name ``manage()`` or a name starting with
+  ``manage_``. If this does not have a corresponding ``<name>__roles__``
+  attribute, one is created with the roles ``('Manager',)``, as a way to
+  automatically protect such methods.
+* Look for any security info object (i.e. an attribute that has an attribute
+  ``__security_info__``). If one is found call its ``apply()`` method with the
+  class as an argument, and then delete it.
+
+  The ``apply()`` method of ``ClassSecurityInfo`` does this:
+
+  * Collect any explicitly set ``__ac_permissions__`` tuple and turn it into
+    internal state, as if the ``ClassSecurityInfo`` had been used to set it,
+    so that it is not lost.
+  * For any attribute declared with ``declarePublic()`` or ``declarePrivate()``,
+    set ``<name>__roles__`` to ``ACCESS_PUBLIC`` or ``ACCESS_PRIVATE`` as
+    appropriate.
+  * Build an ``__ac_permissions__`` tuple from the saved declarations of any
+    protected attributes.
+
+    As a special case, a call to
+    ``security.declareObjectProtected(<permission>)`` will result in a value
+    stored with an empty attribute name, which later translates as setting
+    ``__roles__`` directly on the class.
+
+* Find any ``__ac_permissions__`` on the class (probably created by the
+  security info ``apply()`` call) and call
+  ``AccessControl.Permission.registerPermissions`` with it as an argument.
+  This will register the permission in a global list of known permissions with
+  their default roles (usually ``('Manager',)``) held in that module under the
+  variable ``_ac_permissions``. The mangled permission name (see above) will
+  also be set as a class attribute on the class
+  ``AccessControl.Permission.ApplicationDefaultPermissions``, which is a base
+  class of the application root (``OFS.Application.Application``), hence making
+  the mangled permission names available as (acquirable) class attributes on
+  the application root. The value of this class variable is a tuple with the
+  default roles for that permission.
+* For all permissions in ``__ac_permissions__`` and for all attribute (method)
+  names assigned to each permission, set a class attribute ``<name>__roles__``
+  to a ``PermissionRole`` object. If a default list/tuple of roles was supplied,
+  record this in the ``PermissionRole``, otherwise default to ``('Manager',)``.
+
+Determining which roles have a given permission
+-----------------------------------------------
+
+To perform security checks, it is necessary to compare the roles a user has
+with the roles required for a given permission. The method to determine the
+roles of a permission on a given object is called ``rolesForPermissionOn()``.
+It is found in ``AccessControl.ImplPython``, though a C implementation may
+also be in use.
+
+``rolesForPermissionOn()`` can be called directly, but it should be imported
+from ``AccessControl.PermissionRole`` to ensure the correct implementation (C
+or Python) is used. Alternatively, the correct implementation can be accessed
+by using the ``rolesForPermissionOn()`` method of a ``PermissionRole`` object,
+which will supply the correct permission name and default roles.
+
+The default ``rolesForPermissionOn()`` does the following:
+
+* Mangle the permission name (see above).
+* Walk from the object up the inner (containment) acquisition chain to find an
+  object with the mangled permission name as an attribute. Then:
+
+  * If the attribute is ``None``, this is actually the ``ACCESS_PUBLIC`` marker.
+    Return ``('Anonymous',)``.
+  * If the sequence of roles is a tuple, this is a signal to not acquire roles
+    from parent objects. Stop and return any roles collected by walking the
+    acquisition chain so far plus the roles at the current object.
+  * If the sequence of roles is a list, this is a signal to acquire roles from
+    parent objects. Hence, collect the roles at the current object and continue
+    the walk up the acquisition chain.
+  * If roles is a string, assumed to be a different mangled permission name,
+    this is a signal to delegate to another permission. Continue acquisition
+    from the parent, but discard any roles acquired so far.
+
+* If no object with the managled permission attribute is found, return the
+  default roles. Applicable default roles are stored in each ``PermissionRole``
+  object, but for other types of roles, use ``('Manager',)``.
+* In all cases, if the global variable ``_embed_permission_in_roles`` is true,
+  include the mangled permission name in the list of roles returned (even if
+  an empty list). This is used as a debugging aid.
+
+Checking a permission in a context
+----------------------------------
+
+The most basic permission check can be done using:
+
+.. code-block:: python
+
+  from AccessControl import getSecurityManager
+  sm = getSecurityManager()
+  sm.checkPermission('Some permission', someObject)
+
+This returns either ``1`` or ``None`` to indicate whether the current user
+has such a permission.
+
+The call to ``getSecurityManager()`` returns a security manager instance for the
+current request. A security manager is created using ``newSecurityManager()`` in
+the ``validated_hook`` at the end of traversal (hence note that it is *not* set
+during traversal itself; specifically it is not set when a view adapter is being
+looked up and instantiated and so there is no security information available in
+the ``__init__()`` of a view), which creates a new security manager with a
+context that is aware of the current authenticated user (or ``Anonymous`` if
+there is none).
+
+Again, the security manager may use a C implementation, but the default one
+is defined in ``AccessControl.ImplPython``. The two most important methods on
+this object are ``checkPermission()`` (seen above) and ``validate()``, which
+is used during traversal to validate access to an object and will throw an
+``Unauthorized`` exception if not valid. Both of these delegate to a security
+policy, which will invariably be the ``ZopeSecurityPolicy`` also found in
+``ImplPython`` (or C code) and instantiated once with a module-level call to
+``setDefaultBehaviors()``.
+
+The ``checkPermission()`` implementation in ``ZopeSecurityPolicy`` is relatively
+simple. It uses ``rolesForPermissionOn()`` to discover the roles on the object,
+and then obtains the current user from the security context (passed as a
+parameter to its version of ``checkPermission()``) and calls the user object's
+``allowed()`` method with the object and its roles.
+
+Additionally, if the security policy allows for it (which it will by default),
+checks are made to ensure that if the "execution context" has an owner (e.g. it
+is a through-the-web Python script or template owned by a particular user), the
+owner as well as the current user has the appropriate roles, otherwise access is
+disallowed. Also, if proxy roles are set (again applicable to through-the-web
+scripts), these are allowed to be used in lieu of the user's actual roles.
+
+There are various user implementations that can treat ``allowed()`` differently.
+The most common use in Plone is the ``PropertiedUser`` from
+``Products.PluggableAuthService`` (PAS), though there is also a basic
+implementation in ``AccessControl.users.BasicUser``, and a class called
+``SpecialUser`` in the same module that is used for the ``Anonymous`` user.
+
+The PAS version is only marginally more complex than the ``BasicUser``
+implementation (it deals with roles obtained from groups a user belongs to), so
+we will describe the ``allowed()`` implementation from ``BasicUser`` here:
+
+* If the object's required roles is the special variable
+  ``_what_not_even_god_should_do``   (you couldn't make this up), which
+  corresponds to the ``ACCESS_NONE`` security   declaration (as used by
+  ``declareObjectPrivate()``), immediately disallow access.
+* If the object's required roles is ``None``, which corresponds to the
+  ``ACCESS_PUBLIC`` security declaration (as used by ``declareObjectPublic()``),
+  or if ``Anonymous`` is one of the roles (even if the user is not
+  ``Anonymous``), immediately allow access.
+* If ``Authenticated`` is one of the required roles and the user is not
+  ``Anonymous``, immediately allow access unless the object does not share an
+  acquisition parent with the user folder (this is to avoid users with the same
+  id in different user folders trying to steal each other's access through
+  acquisition tricks). This is referred to as the "context check" below.
+* Check if the user's global roles intersect with the roles required to access
+  the object, and allow access if the user passes the context check.
+* Check if there are any local roles, as defined in the attribute
+  ``__ac_local_roles__``, granted to the user and check these against the
+  required roles (and perform the context check). ``__ac_local_roles__`` may be
+  a dict or a callable that returns a dict, containing a mapping of user (or
+  group, if PAS is used,) ids to local roles granted. The local role check is
+  performed iteratively by walking up the acquisition chain and checking the
+  instances of bound methods, unti the root of the acquisition chain.
+* If none of the above succeed, return ``None`` to indicate that the user is not
+  allowed to access the object.
+
+Validating access to an object
+------------------------------
+
+The second type of security operation provided by the ``SecurityManager`` is to
+check whether the user should be able to access a particular context. This is
+most commonly used during traversal, by way of the user folder's ``validate()``
+method. The version in ``Products.PluggableAuthService.PluggableAuthService``
+does this:
+
+* Get all applicable user ids from the request. Most likely, there is only one,
+  but PAS's modular nature means it is possible more than one plugin will supply
+  a user id.
+* Extract the following information from the published object
+  (``REQUEST['published']``):
+
+  * ``accessed``, the object the published object was accessed through, i.e.
+    the first traversal parent (``request['PARENTS'][0]``).
+  * ``container``, the physical container of the object, i.e. the inner
+    acquisition parent. If the published object is a method, the container is
+    also set to be the method, but stripped of any outer acquisition chains by
+    a call to ``aq_inner()``. If the published object does not have an inner
+    acquisition parent, the traversal parent is used in the same way as it is
+    used to set ``accessed``.
+  * ``name``, the name used to access the object, e.g. a traverasl path element.
+  * ``value``, the object we are validating access to, i.e. the published
+    object.
+
+* If this is the top level user folder and the user is the emergency user,
+  return the user immediately without further authorisation.
+* Otherwise, attempt to authorise the user by creating a new security manager
+  for this user and calling its ``validate()`` method with``accessed``,
+  ``container``, ``name``, and ``value`` as arguments.
+
+The default security manager ``validate()`` method delegates to the equivalent
+method on the ``ZopeSecurityPolicy``. This is a charming 200+ line bundle of
+``if`` statements that does something like this:
+
+* If the ``name`` is an ``aq_*`` attribute other than ``aq_parent``,
+  ``aq_inner`` or ``aq_explicit``, raise ``Unauthorized``.
+* Obtain the ``aq_base``'d version of ``container`` and ``accessed``. If the
+  ``accessed`` parent was not acquisition-wrapped, treat the ``aq_base``'d
+  container as the ``aq_base``'d ``accessed``.
+* The caller may have passed in the required roles already as an optimisation.
+  If not, attempt to get the required roles by calling
+  ``getRoles(container, name, value)``. The Python version of this is defined in
+  ``AccessControl.ZopeSecurityPolicy``. It does the following:
+
+  * If the ``value`` has a ``__roles__`` attribute, and it is ``None``
+    (``ACCESS_PUBLIC``) or a list or tuple of roles, return them. (This probably
+    means the ``value`` is a content object or similar.)
+  * If it is a ``PermissionRole`` object or another object with a
+    ``rolesForPermissionOn()`` method (described above), call this with the
+    ``value`` as an argument and return the results. (This probably means the
+    value is a method.)
+  * If there is no ``__roles__`` attribute, check if we have a ``name``. Return
+    "no roles" if not.
+  * Attempt to find a class for the ``value``'s ``container``. If ``value`` is a
+    method, go via the ``im_self`` attribute to get an instance to use as the
+    ``container``. Then look for a ``<name>__roles__`` attribute on the class.
+    If this is a ``PermissionRole``, call ``rolesForPermissionOn()`` as above;
+    if it is a list, tuple or one of the sentinel values (``ACCESS_PUBLIC``,
+    ``ACCESS_PRIVATE`` or ``ACCESS_NONE``, return it directly.
+
+* If we still have no roles, we may have a primitive or other simple object
+   that is not directly security-aware. We can still try to get security
+   information from the ``container``:
+
+  * If there is no ``container`` passed in, we have no way of inferring one, so
+    all bets are off. Raise ``Unauthorized``.
+  * Attempt to get a ``__roles__`` value from the ``container``. If it is
+    acqusition-wrapped, also try to explicitly acquire ``__roles__`` if it does
+    not have a ``__roles__`` attribute itself.
+
+    If this fails, then we may still be able to get some security assertions
+    from the container (see below), but we only allow this if the ``accessed``
+    parent is the ``container``. If the ``value`` was accessed through a more
+    convoluted acquisition chain, say, we cannot rely solely on container
+    assertions, so we raise ``Unauthorized``.
+  * At this point, there are two possibilities: we have some roles required to
+    access the ``container``, or we have no roles at all, but we accessed the
+    ``value`` directly from its parent ``container``. In both cases, we check
+    container security assertions:
+
+    * If the ``container`` is a tuple or string, and we have gotten this far, we
+      consider access to be allowed and return true. (This can't really happen
+      through URL traversal, but could occur with path traversal).
+    * If the ``container `` is an object with an attribute
+      ``__allow_access_to_unprotected_subobjects__``, obtain this, which can be
+      of three things:
+
+      * An integer or boolean: if set to a truth value, allow access and return
+        true, otherwise raise ``Unauthorized``.
+      * A dictionary: Attempt to look up a truth value in this dictionary by
+        using the accessed ``name`` as a key. If not found or false, raise
+        ``Unauthorized``, otherwise allow access and return true. If the name
+        is not found, default to allowing access.
+      * A callable: Call it with the ``name`` and ``value`` as arguments, and
+        use the return value to determine whether to allow access or raise
+        ``Unauthorized``.
+    * If there is no ``__allow_access_to_unprotected_subobjects__``, raise
+      ``Unauthorized``.
+
+  * If we did manage to get some roles from the container, we still check
+    ``__allow_access_to_unprotected_subobjects__`` as above, but only as a
+    negative: we raise ``Unauthorized`` if access is not allowed, and continue
+    security checking against the roles we found otherwise. In this case, we
+    use the ``container`` (probably a content object) as the ``value`` to check.
+  * At this point, we have roles, and we know the container in theory allows
+    access to the attribute that did not have its own security assertions. We
+    set ``value`` to be the ``container`` so that we can check whether we are in
+    fact allowed to access the container.
+  * We can now check whether the user has the appropriate roles. This is
+    essentially the same logic as in ``checkPermission()`` above, although
+    stated slightly differently.
+
+    * If ``__roles__`` is ``None`` (``ACCESS_PUBLIC``) or contains
+      ``Anonymous``, allow access immediately.
+    * If the execution context is something like a through-the-web Python script
+      owned by a user, we raise ``Unauthorized`` if the owner does not have any
+      of the required roles.
+    * If the execution context has proxy roles, these are allowed to be used
+      to validate access intead of the user's actual roles.
+    * Otherwise, call ``user.allowed()`` to validate access and either return
+      true or raise ``Unauthorized``.
+
+The remainder of the logic in ``validate()`` concerns the case where
+``verbose-security`` is enabled in ``zope.conf``. Various checks are made in
+an attempt to raise ``Unauthorized`` exceptions with meaningful descriptions
+about where in the validation logic access was denied.
+
+Changing permissions
+--------------------
+
+The mapping of permissions to roles can be managed persistently at any object by
+setting the mangled permission attribute (see the description of
+``rolesForPermissionOn()`` above) to a list of roles as an instance variable.
+
+The most basic API to do so is the class
+``AccessControl.Permission.Permission``. This is a transient helper class
+initialised with a (non-mangled) permission name (i.e. the first element in an
+``__ac_permissions__`` tuple), a tuple of attributes the permission applies to
+(i.e. the second element in an ``__ac_permissions__`` item) - referred to as
+the variable ``data`` - and an object where the permission is being managed.
+
+The methods ``getRoles()``, ``setRoles()`` and ``setRole()`` on the
+``Permission`` class allow roles to be obtained and changed.
+
+``getRoles()`` will first attempt to get the mangled permission name attribute
+and return its value.
+
+If it is not set, it will fall back to looping over all the listed attributes
+(``data``) and obtaining the roles from the first one found, taking into account
+the various ways in which ``__roles__`` can be stored. Note that an empty string
+in the tuple of attributes means "check the object itself for a ``__roles__``
+attribute". If ``__roles__`` is a list, it is returned, though if it contains
+the legacy role ``Shared``, this is removed first. The sentinel ``None``
+(``ACCESS_PUBLIC``) is turned into ``['Manager', 'Anonymous']``. If no roles are
+set, the default return value is ``['Manager']``, though another default can be
+supplied as the optional last parameter to ``getRoles()``.
+
+``setRoles()`` will set or delete (if setting to an empty list of roles) the
+mangled permission name as an instance variable on the object. Next, it will
+ensure no other ``__roles__`` or ``<name>__roles__`` *instance* variables have
+been set (class variables are left alone, of course), so that the managled
+permission name attribute is the unambiguous statement of the permission-to-
+role mapping.
+
+Note that for both ``getRoles()`` and ``setRoles()``, the difference between
+a tuple (don't acquire roles) and a list (do acquire) is significant, and
+preserved.
+
+``setRole()`` is used to manage a single role. It takes a role name and a
+boolean to decide whether the role should be set or not. It simply builds the
+appropriate list or tuple based on the current value of ``getRoles()`` and then
+calls ``setRoles()``.
+
+In most cases, it is easier to use the API provided by
+``AccessControl.rolemanager.RoleManager`` to manipulate roles in a particular
+context, rather than using ``Permission`` directly. This class, usually via the
+more specific ``OFS.roles.RoleManager``, is a mixin to most persistent objects
+in Zope. It contains a number of relevant methods:
+
+``ac_inherited_permissions(all=0)``
+  Returns a list of permissions applicable to this class, but not defined on
+  this class directly, by walking the ``__bases__`` of the class. (Note that
+  this not inheritance in the persitent acquisition sense!). If ``all`` is set
+  to a truth value, the permissions on this class are included as well. The
+  return value is an ``__ac_permissions__``-like tuple of tuples. For inherited
+  permissions, the attribute list of each permission entry will be an empty
+  tuple.
+``permission_settings(permission=None)``
+  Returns the settings for a single or all permissions, returning a list of
+  dicts. Used mainly by ZMI screens.
+``manage_role(role, permissions=[])``
+  Uses the ``Permission`` API to grant the role to the permissions passed in,
+  and take it away from any other permissions where the role may be set.
+``manage_acquiredPermissions(permissions=[])``
+  Uses the ``Permission`` API to set the roles lists for each of the passed-in
+  permissions to a list (acquire), and for all other permissions to a tuple
+  (don't acquire).
+``manage_permission(permission, roles=[], acquire=0)``
+  Uses the ``Permission`` API to set roles for the given permission to either a
+  tuple or list (it does not matter what type of sequence the ``roles``
+  parameter contains, the ``acquire`` parameter is used), but only if the
+  permission is known to this object.
+``permissionsOfRole(role)``
+  Uses the ``Permission`` API to get the permissions of the given role. Returns
+  a list of dicts with keys ``name`` and ``selected`` (set to either an empty
+  string or the string ``SELECTED``).
+``rolesOfPermission(permission)``
+  The inverse of ``permissionsOfRole()``, returning a similar data structure.
+``acquiredRolesAreUsedBy(permission)``
+  Returns either ``CHECKED`` or an empty string, depending on whether the roles
+  sequence of the given permission is a list or tuple.
+
+The use of the strings ``CHECKED`` or ``SELECTED`` as booleans is an unfortunate
+side-effect of these methods being used quite literally by ZMI templates.
+
+Global and local roles
+----------------------
+
+The list of known (valid) roles in any context is set in the attribute
+``__ac_roles__``. On the initalisation of the application root during startup,
+in ``install_required_roles()`` in ``OFS.Application.AppInitializer``, this is
+made to include at least ``Owner`` and ``Authenticated``. The ``RoleManager``
+base class set it as a class variable to contain
+``('Manager', 'Owner', 'Anonymous', 'Authenticated')``.
+
+In ``AccessControl.rolemanager.RoleManager``, the method ``valid_roles()`` can
+be used to obtain the list of valid roles in any given context. It will also
+include roles from any parent objects referenced via a ``__parent__``
+attribute.
+
+User defined roles can be set through the ZMI or the method ``_addRole()`` in
+the ``OFS.roles.RoleManager`` specialisation, which simply manipulates the
+``__ac_roles__`` tuple as an instance variable. There is also ``_delRoles()`` to
+delete roles. The method ``userdefined_roles()`` on the base
+``AccessControl.rolemanager.RoleManager`` class will return a list of all roles
+set as instance variables instead of class variables.
+
+The global roles of a given user is determined by the ``getRoles()`` function
+on the user object (see the description of the ``allowed()`` method above).
+The default ``ZODBRoleManager`` plugin for PAS stores a mapping of users and
+roles persistently in the ZODB, though other implementations are possible, e.g.
+querying an LDAP repository.
+
+Users may also have local roles, granted in a particular container and its
+children. These can be discovered for a given user most easily by calling the
+``getRolesInContext()`` function on a user object, which takes a context object
+as a parameter.
+
+Local roles are stored in the instance variable ``__ac_local_roles__``. This may
+be a dict or a callable that returns a dict, containing a mapping of user (or
+group) ids to local roles granted. The local role check is performed iteratively
+by walking up the acquisition chain and checking the instances of bound methods,
+until the root of the acquisition chain is reached.
+
+The API to manage local role assignments in a given context is found in
+``AccessControl.rolemanager.RoleManager``, through the following methods:
+
+``get_local_roles()``
+  Return a tuple of local roles, each represented as a tuple of user ids and
+  a tuple of local roles for that user id. With PAS, this may also include
+  group ids.
+``users_with_local_role(role)``
+  Inspect ``__ac_local_roles__`` to get a list of all users with the given local
+  role.
+``get_local_roles_for_userid(userid)``
+  Inspect ``__ac_local_roles__`` to get a tuple of all local roles for the given
+  user id.
+``manage_addLocalRoles(userid, roles)``
+  Modify ``__ac_local_roles__`` to add the given roles to the given user id. Any
+  existing roles are kept.
+``manage_setLocalRoles(userid, roles)``
+  Modify ``__ac_local_roles__`` to add the given roles to the given user id. Any
+  existing roles are replaced.
+``manage_delLocalRoles(userids)``
+  Remove all local roles for the given user ids.
+
+Emergency users
+---------------
+
+On startup, at import time of ``AccessControl.users``, the function
+``readUserAccessFile()`` is called to look for a file called ``accesss`` in the
+Zope ``INSTANCE_HOME`` (an environment variable) directory. If found, it reads
+the first line and parses it to return a tuple ``(name, password, domains,
+remote_user_mode,)``.
+
+If set, the module variable ``emergency_user`` is set to an
+``UnrestrictedUser``, a special type of user where the ``allowed()`` method
+always returns true. If not, it is set to a ``NullUnrestrictedUser``, which
+acts in reverse and disallows everything.
+
+The user folder implementations in ``AccessControl`` and PAS make specific
+checks for this user during authentication and permission validation to ensure
+this user can always log in and has virtually any permission, with the exception
+of ``_what_not_even_god_should_do`` (``ACCESS_NONE``).
\ No newline at end of file

Added: zope_secrets/trunk/source/startup.rst
===================================================================
--- zope_secrets/trunk/source/startup.rst	                        (rev 0)
+++ zope_secrets/trunk/source/startup.rst	2012-01-02 12:13:10 UTC (rev 123874)
@@ -0,0 +1,438 @@
+==================================
+Startup and product initialisation
+==================================
+
+.. admonition:: Description
+
+        What happens on Zope startup, and how do Zope 2 products and
+        constructors work?
+
+.. contents :: :local:
+
+What happens on Zope startup?
+-----------------------------
+
+A startup script (e.g. ``bin/instance fg``) calls Zope 2's ``run.py`` in an
+appropriate interpreter context (i.e. one that has the necessary packages on
+``sys.path``). This invokes a subclass of ``ZopeStarter`` from
+``Zope2.Startup``:
+
+.. code-block:: python
+
+        import Zope2.Startup
+        starter = Zope2.Startup.get_starter()
+        opts = _setconfig()
+        starter.setConfiguration(opts.configroot)
+        starter.prepare()
+        starter.run()
+
+There are various variants that allow different ways to supply configuration.
+
+There are two versions of the starter, one for Unix and one for Windows. It
+performs a number of actions during the ``prepare()`` phase:
+
+.. code-block:: python
+
+    def prepare(self):
+        self.setupInitialLogging()
+        self.setupLocale()
+        self.setupSecurityOptions()
+        self.setupPublisher()
+        # Start ZServer servers before we drop privileges so we can bind to
+        # "low" ports:
+        self.setupZServer()
+        self.setupServers()
+        # drop privileges after setting up servers
+        self.dropPrivileges()
+        self.setupFinalLogging()
+        self.makeLockFile()
+        self.makePidFile()
+        self.setupInterpreter()
+        self.startZope()
+        self.serverListen()
+        from App.config import getConfiguration
+        config = getConfiguration()
+        self.registerSignals()
+        # emit a "ready" message in order to prevent the kinds of emails
+        # to the Zope maillist in which people claim that Zope has "frozen"
+        # after it has emitted ZServer messages.
+
+        logger.info('Ready to handle requests')
+        self.sendEvents()
+
+Mostly, this is about using information from the configuration (read using
+``ZConfig`` from a configuration file, or taken from the global defaults) to
+set various module level variables and options.
+
+The ``startZope()`` call ends up in ``Zope2.App.startup.startup()``, which
+performs a number of startup tasks:
+
+* Importing products (``OFS.Application.import_products()``)
+* Creating a ZODB for the chosen storage (as set in the ``ZConfig``
+  configuration). This is stored in both ``Globals.DB`` and ``Zope2.DB``, and is
+  configured using a ``dbtab`` (mount points) read from the configuration file.
+  When this is done, the event ``zope.processlifetime.DatabaseOpened`` is
+  notified.
+* Setting the ``ClassFactory`` on the ZODB instance to
+  ``Zope2.App.ClassFactory.ClassFactory``. This is a function that will attempt
+  to import a class, and will return ``OFS.Uninstalled.Broken`` if the class
+  cannot be imported for whatever reason. This allows for somewhat graceful
+  recovery if symbols that are persistently referenced in the ZODB disappear.
+* Loading ZCML configuration from ``site.zcml``. This in turn loads ZCML for all
+  installed products in the ``Products.*`` namespace, and ZCML slugs. The
+  ``load_zcml()`` call also sets up a ``Zope2VocabularyRegistry``.
+* Creating the ``app`` object, an instance of
+  ``App.ZApplication.ZApplicationWrapper`` that wraps a
+  ``OFS.Application.Application``. The purpose of the wrapper is to:
+
+  * Create an instance of the application object at the root of the ZODB on
+    ``__init__()`` if not there already. The name by default is ``Application``.
+  * Implement traversal over this wrapper (``__bobo_traverse__``) to open a ZODB
+    connection before continuing traversal, and closing it at the end of the
+    request.
+  * Return the persistent instance of the true application root object when
+    called.
+
+  The wrapper is set as ``Zope2.bobo_application``, which is used when the
+  publisher publishes the ``Zope2`` module - more on publication later.
+* Initialising the application object using ``OFS.Application.initialize()``.
+  This defensively creates a number of items:
+
+  .. code-block:: python
+
+        def initialize(self):
+            # make sure to preserve relative ordering of calls below.
+            self.install_cp_and_products()
+            self.install_tempfolder_and_sdc()
+            self.install_session_data_manager()
+            self.install_browser_id_manager()
+            self.install_required_roles()
+            self.install_inituser()
+            self.install_errorlog()
+            self.install_products()
+            self.install_standards()
+            self.install_virtual_hosting()
+
+* Notfiying the event ``zope.processlifetime.DatabaseOpenedWithRoot``
+* Setting a number of ZPublisher hooks:
+
+  .. code-block:: python
+
+    Zope2.zpublisher_transactions_manager = TransactionsManager()
+    Zope2.zpublisher_exception_hook = zpublisher_exception_hook
+    Zope2.zpublisher_validated_hook = validated_hook
+    Zope2.__bobo_before__ = noSecurityManager
+
+The ``run()`` method of the ``ZopeStarter`` then runs the main startup loop
+(note: this is not applicable for WSGI startup using ``make_wsgi_app()`` in
+``run.py``, where the WSGI server is responsible for the event loop):
+
+.. code-block:: python
+
+    def run(self):
+        # the mainloop.
+        try:
+            from App.config import getConfiguration
+            config = getConfiguration()
+            import ZServer
+            import Lifetime
+            Lifetime.loop()
+            sys.exit(ZServer.exit_code)
+        finally:
+            self.shutdown()
+
+The ``Lifetime`` module uses ``asyncore`` to poll for connected sockets until
+shutdown is initiated, either through a signal or an explicit changing of the
+flag ``Lifetime._shutdown_phase``, which is checked for each iteraton of the
+loop.
+
+Sockets are created when new connections are received on a defined server. When
+using the built-in ZServer (i.e. not WSGI), the default HTTP server is defined
+in ``ZServer.HTTPServer.zhttp_server``, which derives from
+``ZServer.medusa.http_server``, which in turn is an ``asyncore.dispatcher``.
+
+Servers are created in ``ZopeStarter.setupServers()``, which loops over the
+``ZConfig``-defined server factories and call their ``create()`` metohod. The
+server factories are defined in ``ZServer.datatypes``. (The word ``datatypes``
+refers to ``ZConfig`` data types.)
+
+Note also that some of the configuration data is mutated in the ``prepare()``
+method of the server instance, which is called from
+``Zope2.startup.handlers.root_handler()`` during the configuration phase. These
+handlers are registered with a call to ``Zope2.startup.handlers.handleConfig()``
+during the ``_setconfig()`` call in ``run.py``.
+
+How are products installed?
+---------------------------
+
+During application initialisation, the method ``install_products()`` will call
+the method ``OFS.Application.install_products()``. This will record products
+in the ``Control_Panel`` if this is enabled in ``zope.conf``, and call the
+``initialize()`` function for any product that has one with a *product context*
+that allows the product to register constructors for the Zope runtime.
+
+``install_products()`` loops over all product directories (configured via
+``zope.conf`` and kept in ``Products.__path___`` by
+``Zope2.startup.handlers.root_handler()``) and scans these for product
+directories with an ``__init__.py``. For each, it calls
+``OFS.Application.install_product``. This will:
+
+* Import the product as a Python package
+* Look for an attribute ``misc_`` at the product root, which is used to store
+  things like icons. If it is a dict, wrap it in an ``OFS.misc_.Misc_`` object,
+  which is just a simple, security-aware class. Then store a copy of it as an
+  attribute on the object ``Application.misc_``. The attribute name is the
+  product name. This allows traversal to the ``misc_`` resources.
+
+  As an example of the use of the use of ``misc_``, consider this dict set up
+  in ``Products/CMFPlone/__init__.py``:
+
+  .. code-block:: python
+
+    misc_ = {'plone_icon': ImageFile(
+              os.path.join('skins', 'plone_images', 'logoIcon.png'),
+              cmfplone_globals)}
+
+  This can now be traversed to as ``/misc_/CMFPlone/plone_icon`` by virtue
+  of the ``misc_`` attribute on the application root.
+* Next, create an ``App.ProductContext.ProductContext`` to be used during
+  product initialisation. This is passed a ``product`` object, a handle to the
+  application root, and the product's package.
+
+  There are two ways to obtain the ``product`` object:
+
+  If persistent product installation (in the ``Control_Panel``) is enabled in
+  ``zope.conf``, call ``App.Product.initializeProduct``. This will create a
+  ``App.Product.Product`` object and save it persistently in
+  ``App.Control_Panel.Products``. It also reads the file ``version.txt`` from
+  the product to determine a version number, and will change the persistent
+  object (at Zope startup) if the version has changed. The ``Product`` object is
+  initialised with a product name and title and is used to store basic
+  information about the product. The ``Product`` object is then returned.
+
+  If persistent product installation is disabled (the default), simply
+  instantiate a ``FactoryDispatcher.Product`` object (which is a simpler,
+  duck-typing-equivalent of ``App.Product.Product``) with the product name.
+
+* If the product has an ``initialize()`` method at its root, call it with the
+  product context as an argument.
+
+Once old-style products are initialised, any packages outside the ``Products.*``
+namespace that want to be initialised are processed. The
+``<five:registerProduct />`` ZCML directive stores a list of packages to be
+processed and any referenced ``initialize()`` method in the variable
+``OFS.metaconfigure._packages_to_initialize``, accessible via the function
+``get_packages_to_initialize()`` in the same module. ``install_products()``
+loops over this list, calling ``install_package()`` for each. This works very
+much like ``install_product()``. When it is done, it calls the function
+``OFS.metaconfigure.package_initialized()`` to remove the package from the
+list of packages to initalise.
+
+How do Zope 2 product constructors work?
+----------------------------------------
+
+Products can make constructors available to the Zope runtime. This is what
+powers the ``Add`` drop-down in the ZMI, for instance. They do so by calling
+``registerClass()`` on the product context passed to the ``initialize()``
+function. This takes the following main arguments:
+
+``instance_class``
+  The class of the object that will be created.
+``meta_type``
+  A unique string representing kind of object being created, which appears in
+  add lists. If not specified, then the class ``meta_type`` will be used.
+``permission``
+  The permission name for the constructors. If not specified, a permission name
+  generated from the meta type (``"Add <meta_type>"``) will be used.
+``constructors``
+  A list of constructor methods. An element in the list can be a callable object
+  with a ``__name__`` attribute giving the name the method should have in the
+  product, or the a tuple consisting of a name and a callable
+  object. The first method will be used as the initial method called
+  when creating an object through the web (in the ZMI).
+
+  It is quite common to pass in two constructor callables: one that is a
+  ``DTMLMethod`` or ``PageTemplateFile`` that renders an add form and one that
+  is a method that actually creates and adds an instance. A typical example from
+  ``Products.MailHost`` is:
+
+  .. code-block:: python
+
+    manage_addMailHostForm = DTMLFile('dtml/addMailHost_form', globals())
+
+    def manage_addMailHost(self,
+                           id,
+                           title='',
+                           smtp_host='localhost',
+                           localhost='localhost',
+                           smtp_port=25,
+                           timeout=1.0,
+                           REQUEST=None,
+                          ):
+        """ Add a MailHost into the system.
+        """
+        i = MailHost(id, title, smtp_host, smtp_port)
+        self._setObject(id, i)
+
+        if REQUEST is not None:
+            REQUEST['RESPONSE'].redirect(self.absolute_url()+'/manage_main')
+
+  These are then referenced in ``initialize()``:
+
+  .. code-block:: python
+
+    def initialize(context):
+      context.registerClass(
+          MailHost.MailHost,
+          permission='Add MailHost objects',
+          constructors=(MailHost.manage_addMailHostForm,
+                        MailHost.manage_addMailHost),
+          icon='www/MailHost_icon.gif',
+      )
+
+  The form will be called with a path like
+  ``/<container>/manage_addProduct/MailHost/manage_addMailHostForm``. The
+  ``<form />`` on this page has a relative URL ``action="manage_addMailHost"``,
+  which means that when the form is submitted, the ``manage_addMailHost()``
+  function is called. ``id``, ``title`` and the other variables are passed as
+  request parameters and marshalled (by ``mapply()`` - see below) into function
+  arguments, and the ``REQUEST`` is implicitly passed (again by ``mapply()``).
+``icon``
+  The name of an image file in the package to be used for instances. The class
+  ``icon`` attribute will be set automagically if an icon is provided.
+``permissions``
+  Additional permissions to be registered.
+``visibility``
+  The string ``"Global"`` if the object is globally visible, or ``None``
+  otherwise.
+``interfaces``
+  A list of the interfaces the object supports. These can be used to filter
+  addable meta-types later.
+``container_filter``
+  A function that is called with an ``ObjectManager`` object as the only
+  parameter, which should return a truth value if the object is happy to be
+  created in that container. The filter is called before showing
+  ``ObjectManager``'s ``Add`` list, and before pasting (after object copy or
+  cut), but not before calling an object's constructor.
+
+The main aims of this method are to register some new permissions, store
+some information about the class in the variable ``Products.meta_types``, and
+create a ``FactoryDispatcher`` that allow traversal to the constructor method.
+
+* If an ``icon`` and ``instance_class`` are supplied, set an ``icon`` attribute
+  on ``instance_class`` to a path like ``misc_/<productname>/<iconfilename>``.
+* Register any ``permissions`` by calling
+  ``AccessControl.Permission.registerPermissions()`` (described later).
+* If there is no ``permission`` provided, generate a permission name as the
+  string "Add <meta_type>", defaulting to being granted to ``Manager`` only.
+  Register this permission as well.
+* Grab the name of the first constructor passed in the ``constructors`` tuple.
+  This can either be the function's ``__name__``, or a name can be provided
+  explicitly by passing as the first list element a tuple of
+  ``(name, function)``.
+* Try to obtain the value of the symbol ``__FactoryDispatcher__`` in the
+  package root (``__init__.py``) if set. If not, create a class on the fly with
+  this name  by deriving from ``App.FactoryDispatcher.FactoryDispatcher`` and
+  set this onto the product package as an attribute named
+  ``__FactoryDispatcher__``.
+* Set an attribute ``_m`` in the package root if it does not exist to an
+  instance of ``AttrDict`` wrapped around the factory dispatcher. This is a
+  bizzarre construction best described by its implementation:
+
+  .. code-block:: python
+
+    class AttrDict:
+
+      def __init__(self, ob):
+          self.ob = ob
+
+      def __setitem__(self, name, v):
+          setattr(self.ob, name, v)
+
+* If no ``interfaces`` were passed in explicitly, obtain the interfaces
+  implemented by the ``instance_class``, if provided.
+* Record information about the primary constructor in the tuple
+  ``Products.meta_types`` by appending a dict with keys:
+
+  ``name``
+    The ``meta_type`` passed in or obtained from the ``instance_class``.
+  ``action``
+    A path segment like ``manage_addProduct/<productname>/<constructorname>``.
+    for the initial (first) constructor. More on ``manage_addProduct`` below.
+  ``product``
+    The name of the product, without the ``Product.`` prefix.
+  ``permission``
+    The add permission passed in or generated.
+  ``visibility``
+    Either ``"Global"`` or ``None`` as passed in to the method.
+  ``interfaces``
+    The list of interfaces passed in or obtained from ``instance_class``.
+  ``instance``
+    The ``instance_class`` as passed in to the method.
+  ``container_filter``
+    The ``container_filter`` as passed in to the method.
+* Next, put the initial constructor and any further constructors passed in onto
+  the ``_m`` pseudo-dictionary (which really just means setting them as
+  attributes on the ``FactoryDispatcher``-subclass). The appropriate
+  ``<methodname>__roles__`` attribute is set to a ``PermissionRole`` describing
+  the add permission as well.
+* If an ``icon`` filename was passed in, construct an ``ImageFile`` to read the
+  icon file from the package and stash it in the ``OFS.misc_.misc_`` class so
+  that it can be traversed to later.
+
+Note that previously, the approach taken was to inject factory methods into
+the class ``OFS.ObjectManager.ObjectManager``, which is the base class for most
+folderish types in Zope. This is still supported for backwards compatibility,
+by providing a ``legacy`` tuple of function objects, but is deprecated.
+
+``Products.meta_types`` is used in various places, most notably in
+``OFS.ObjectManager.ObjectManager`` in the methods ``all_meta_types()`` and
+``filtered_meta_types()``.
+
+The former returns all of ``Products.meta_types`` (plus possibly some legacy
+entries in ``_product_meta_types`` on the application root object, used to
+support through-the-web defined products via
+``App.ProductRegistry.ProductRegistry``), applying the ``container_filter`` if
+available and optionally filtering by ``interfaces``.
+
+The latter is used to power the ``Add`` widget in the ZMI by creating a
+``<select />`` box for all ``meta_types`` the user is allowed to add by checking
+the add permission of each of the items returned by ``all_meta_types()``. The
+``action`` stored in the ``meta_types`` list is then used to traverse to and
+invoke a constructor.
+
+Note that subclasses of ``ObjectManager`` may sometimes override
+``all_meta_types()`` to set a more restrictive list of addable types. They may
+also add to the list of the default implementation by setting a ``meta_types``
+class or instance variable containing further entries in the same format as
+``Products.meta_types``.
+
+Finally, let us consider the ``manage_addProduct`` method seen in the ``action``
+used to traverse to a registered constructor callable (e.g. an add form) using
+a path such as ``/<container>/manage_addProduct/<productname>/<constructname>``.
+It is set on ``OFS.ObjectManager.ObjectManager``, and is actually an instance of
+``App.FactoryDispatcher.ProductDispatcher``. This is an implicit-acquisition
+capable object that implements ``__bobo_traverse__`` as follows:
+
+* Attempt to obtain a ``__FactoryDispatcher__`` attribute from the product
+  package (from the name being traversed to), defaulting to the standard
+  ``FactoryDispatcher`` class in the same module.
+* Find a persistent ``App.Product.Product`` if there is one, or create a
+  simple ``App.FactoryDispatcher.Product`` wrapper if persistent product
+  installation has not taken place.
+* Create an instance of the factory dispatcher on the fly, passing in the
+  product descriptor and the parent object (i.e. the continer).
+* Return this, acquisition-wrapped in ``self``, to allow traversal to continue.
+
+Traversal then continues over the ``FactoryDispatcher``. In the version of
+this created by ``registerClass()``, each constructor is set as an attribute
+on the product-specific dispatcher, with appropriate roles, so traversal will be
+able to obtain the constructor callable.
+
+There is also a fallback ``__getattr__()`` implementation in the base
+``FactoryDispatcher`` class, which will inspect the ``_m`` attribute on the
+product package for an appropriate constructor, and is also able to obtain
+constructor information from a persistent ``Product`` instance (from
+``Control_Panel`` if there was one. This supports a (legacy) approach where
+instead of calling ``registerClass()`` to register constructors, constructors
+are set in a dict called``_m`` at the root of the product.
\ No newline at end of file



More information about the checkins mailing list