[Repoze-checkins] r717 - compoze/trunk/compoze playground/trunk/chris playground/trunk/chris/solver repoze.decsec repoze.decsec/trunk repoze.decsec/trunk/repoze repoze.decsec/trunk/repoze/decsec repoze.decsec/trunk/repoze/decsec/etc repoze.errorlog/trunk repoze.project/trunk repoze.project/trunk/repoze/project

Chris McDonough chrism at agendaless.com
Thu Feb 21 08:06:08 UTC 2008


Author: Chris McDonough <chrism at agendaless.com>
Date: Thu Feb 21 03:06:07 2008
New Revision: 717

Log:
A first cut (untested) at an ACL-based declarative security middleware.


Added:
   repoze.decsec/
   repoze.decsec/trunk/
   repoze.decsec/trunk/CHANGES.txt
   repoze.decsec/trunk/COPYRIGHT.txt
   repoze.decsec/trunk/LICENSE.txt
   repoze.decsec/trunk/README.txt
   repoze.decsec/trunk/TODO.txt
   repoze.decsec/trunk/ez_setup.py   (contents, props changed)
   repoze.decsec/trunk/repoze/
   repoze.decsec/trunk/repoze/__init__.py   (contents, props changed)
   repoze.decsec/trunk/repoze/decsec/
   repoze.decsec/trunk/repoze/decsec/__init__.py   (contents, props changed)
   repoze.decsec/trunk/repoze/decsec/etc/
   repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini
   repoze.decsec/trunk/repoze/decsec/interfaces.py   (contents, props changed)
   repoze.decsec/trunk/repoze/decsec/middleware.py   (contents, props changed)
   repoze.decsec/trunk/repoze/decsec/sample.py   (contents, props changed)
   repoze.decsec/trunk/repoze/decsec/tests.py   (contents, props changed)
   repoze.decsec/trunk/setup.py   (contents, props changed)
Modified:
   compoze/trunk/compoze/fetcher.py
   playground/trunk/chris/solver/solve.py
   playground/trunk/chris/solver/solver.txt
   playground/trunk/chris/zopesetup.py
   repoze.errorlog/trunk/CHANGES.txt
   repoze.errorlog/trunk/setup.py
   repoze.project/trunk/CHANGES.txt
   repoze.project/trunk/repoze/project/bootstrap.py
   repoze.project/trunk/setup.py

Modified: compoze/trunk/compoze/fetcher.py
==============================================================================
--- compoze/trunk/compoze/fetcher.py	(original)
+++ compoze/trunk/compoze/fetcher.py	Thu Feb 21 03:06:07 2008
@@ -107,8 +107,9 @@
 
     def download_distributions(self):
 
-        # First, collect best sdist candidate for the requirment from each 
+        # First, collect best sdist candidate for the requirement from each 
         # index into a self.tmpdir
+        
         # XXX ignore same-name problem for now
 
         for index_url in self.options.index_urls:

Modified: playground/trunk/chris/solver/solve.py
==============================================================================
--- playground/trunk/chris/solver/solve.py	(original)
+++ playground/trunk/chris/solver/solve.py	Thu Feb 21 03:06:07 2008
@@ -1,9 +1,9 @@
 from pprint import pprint
+import sys
 
 from pkg_resources import Requirement
 from pkg_resources import Distribution
 from pkg_resources import parse_version
-from constraint import Problem
 
 def make_dummy_index():
     r = Requirement.parse
@@ -15,6 +15,9 @@
     index['a'] = {
         d('a', '1.0') : ( r('b>=1.0'), r('c>=1.0'), r('d>=2.0'), ),
         }
+    index['a[pdf]'] = { # 'a' package with 'pdf' extra reqiring foo
+        d('a', '1.0') : ( r('b>=1.0'), r('c>=1.0'), r('d>=2.0'), r('foo') ),
+        }
     index['b'] = {
         d('b', '1.1') : ( r('d>=2.0'), ),
         d('b', '1.2') : ( r('d==2.1'), ),
@@ -40,24 +43,18 @@
 class NotFound:
     pass
 
-class NoDependencies:
-    project_name = None
+class Root:
+    pass
 
 def random_solution(solutions):
     return solutions[0]
 
 class RequirementContext:
-    def __init__(self, reqt, index, verbose=False):
-        self.reqt = reqt
+    def __init__(self, reqts, index, verbose=False):
+        self.reqts = reqts
         self.index = index
-        self.graph = self.make_graph(reqt)
-        self.verbose = verbose
+        self.graph = self.make_graph()
 
-    def warn(self, msg):
-        if self.verbose:
-            import sys
-            sys.stderr.write('WARNING: %s\n' % msg)
-            
     def distributions(self, reqt):
         """
         Return all the distributions in the index matching reqt.
@@ -76,10 +73,13 @@
         """
         return self.index[distribution.project_name][distribution]
 
-    def make_graph(self, reqt):
+    def make_graph(self):
         """
         Make a graph based on our index data in the form:
 
+                    root
+                      |
+                      |
                    reqt "a"
                    /     \
                dist a1  dist a2
@@ -91,8 +91,8 @@
         Leaf nodes in the graph are either a distribution with no
         requirements or a NotFound node.
         """
-        graph = {}
-        stack = [reqt]
+        graph = {Root:self.reqts}
+        stack = self.reqts[:]
         while stack:
             reqt = stack.pop()
             distributions = self.distributions(reqt)
@@ -111,12 +111,12 @@
         Return a list of lists, where each inner list represents a
         path through the graph ending in a node that has no
         dependencies (or a NotFound node).  The entry in the inner
-        list is a (reqt, distribution) tuple.
+        list is a (distribution, reqt) tuple.
         """
         if path is None:
             path = []
         if node is None:
-            node = self.reqt
+            node = Root
         path = path + [node]
         children = self.graph.get(node)
         if not children:
@@ -131,37 +131,50 @@
 
     def candidates(self):
         """
-        Return { distribution_a: [ path1, path2, ...] }
+        Return { reqt_a: [ path1, path2, ...] }
         """
         candidates = {}
         warnings = {}
         paths = self.paths()
 
         for path in paths:
-            unflattened = unflatten(path)
-            first, rest = unflattened[0], unflattened[1:]
-            reqt, dist = first
-            pathlist = candidates.setdefault(dist, [])
-            pathlist.append(rest)
+            root, rest = path[0], path[1:]
+            if not rest:
+                continue
+            reqt = rest[0]
+            pathlist = candidates.setdefault(reqt, [])
+            last = rest[-1]
+            if last is NotFound:
+                badreqt = rest[-2]
+                badpath = ' --> '.join([ str(x) for x in rest[:-1] ])
+                self.warn(
+                    'Distribution satisfying %s was not found in the index '
+                    'via path %s' % (badreqt, badpath)
+                    )
+                continue
+            unflattened = unflatten(rest)
+            pathlist.append(unflattened)
 
         return candidates
 
+    def warn(self, msg):
+        sys.stderr.write(msg+'\n')
+        sys.stderr.flush()
+
 def unflatten(L):
     # unflatten an even-length list L(1,2,3,4) into L((1,2),(3,4))
     slice0 = L[::2]
     slice1 = L[1::2]
     return zip(slice0, slice1)
 
-def solve(contexts, chooser):
-    problem = Problem()
-    for context in contexts:
-        paths = context.candidates()
-        # do something clever with each path
-        pprint(paths)
+def solve(context):
+    paths = context.candidates()
+    # do something clever with each path
+    pprint(paths)
 
 if __name__ == '__main__':
-    import sys
-    reqts = [ Requirement.parse(reqt_str) for reqt_str in sys.argv[1:] ]
     index = make_dummy_index()
-    contexts = [ RequirementContext(reqt, index, False) for reqt in reqts ]
-    solve(contexts, random_solution)
+    reqt_strings = set(sys.argv[1:])
+    reqts = [ Requirement.parse(reqt_str) for reqt_str in reqt_strings ]
+    context = RequirementContext(reqts, index, False)
+    solve(context)

Modified: playground/trunk/chris/solver/solver.txt
==============================================================================
--- playground/trunk/chris/solver/solver.txt	(original)
+++ playground/trunk/chris/solver/solver.txt	Thu Feb 21 03:06:07 2008
@@ -240,26 +240,19 @@
     graph rooted at a single requirement, we want to build a mapping
     in the form::
 
-      { distribution_a: [ path1, path2, ...] }
+      { requirement: [ path1, path2, ...], ... }
 
-    Where 'path1' and 'path2' (etc) are lists in the form::
+    Where 'path1' and 'path2' (etc) are each lists in the form::
 
-      [ (reqt_r1, distribution_r1), (reqt_r2, distribution2_r2), ... ]
+      [ distribution_r1, distribution_r2, ... ]
 
     'distribution_r1' and 'distribution_r2' etc. represent the
-    requirement dependencies of 'distribution_a'.  'reqt_r1' and
-    'reqt_r2' represent the requirement in 'distribution_a' that
-    specified the associated distribution.  The last distribution in
-    the list will either be a distribution without any dependencies or
-    a NotFound node.
-
-    XXX from here on in is misinformation.
-
-    Because both of these paths begin with the same two package names
-    ((a,b)), they are grouped into a path set.  As a result, we will
-    only have one path set to consult.  The path set is::
+    transitive dependencies of the requirement specified by
+    'requirement'.  Paths that end in NotFound are filtered and warned
+    about.
 
-            [ (dist a1, dist b1), (dist a2, dist b2) ]
+    At this point, for each requirement specified on the command line,
+    we have zero or more paths which are capable of solving each.
 
     We will use each path in a path set as input to a constraint
     domain to see which path can be solved by our index.  You may

Modified: playground/trunk/chris/zopesetup.py
==============================================================================
--- playground/trunk/chris/zopesetup.py	(original)
+++ playground/trunk/chris/zopesetup.py	Thu Feb 21 03:06:07 2008
@@ -1,4 +1,4 @@
-__version__ = '2.10.4.2'
+__version__ = '2.10.5.0'
 
 import os
 import shutil
@@ -58,7 +58,8 @@
     def get_packages(self):
         return self._pkgs.keys()
 
-IGNORE_EXTS = ('.pyc', '.pyo', '.c', '.h', '.so', '.o', '.dll', '.lib', '.obj', '.cfg')
+IGNORE_EXTS = ('.pyc', '.pyo', '.c', '.h', '.so', '.o', '.dll', '.lib',
+               '.obj', '.cfg')
 
 def remove_stale_bytecode(arg, dirname, names):
     names = map(os.path.normcase, names)
@@ -101,16 +102,8 @@
         self.cmdclass['build_ext'] = MyExtBuilder
 
 # All extension modules must be listed here.
-ext_modules = [
-
-    # AccessControl
-    Extension(name='AccessControl.cAccessControl',
-              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['Acquisition'],
-              sources=['AccessControl/cAccessControl.c'],
-              depends=['ExtensionClass/ExtensionClass.h',
-                       'ExtensionClass/pickle/pickle.c',
-                       'Acquisition/Acquisition.h']),
 
+zodb_ext_modules = [
     # BTrees
     Extension(name='BTrees._OOBTree',
               include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['persistent'],
@@ -135,6 +128,42 @@
               define_macros=[('EXCLUDE_INTSET_SUPPORT', None)],
               sources=['BTrees/_fsBTree.c']),
 
+    #ZODB
+    Extension(name = 'persistent.cPersistence',
+              include_dirs = ['persistent'],
+              sources= ['persistent/cPersistence.c',
+                        'persistent/ring.c'],
+              depends = ['persistent/cPersistence.h',
+                         'persistent/ring.h',
+                         'persistent/ring.c']
+              ),
+    Extension(name = 'persistent.cPickleCache',
+              include_dirs = ['persistent'],
+              sources= ['persistent/cPickleCache.c',
+                        'persistent/ring.c'],
+              depends = ['persistent/cPersistence.h',
+                         'persistent/ring.h',
+                         'persistent/ring.c']
+              ),
+    Extension(name = 'persistent.TimeStamp',
+              sources= ['persistent/TimeStamp.c']
+              ),
+    Extension(name = 'ZODB.winlock',
+              sources = ['ZODB/winlock.c']
+              ),
+
+]
+
+zope_ext_modules = [
+
+    # AccessControl
+    Extension(name='AccessControl.cAccessControl',
+              include_dirs=EXTENSIONCLASS_INCLUDEDIRS+['Acquisition'],
+              sources=['AccessControl/cAccessControl.c'],
+              depends=['ExtensionClass/ExtensionClass.h',
+                       'ExtensionClass/pickle/pickle.c',
+                       'Acquisition/Acquisition.h']),
+
     # DocumentTemplate
     Extension(name='DocumentTemplate.cDocumentTemplate',
               include_dirs=EXTENSIONCLASS_INCLUDEDIRS,
@@ -201,36 +230,6 @@
     Extension(name='Products.ZCTextIndex.okascore',
               sources=['Products/ZCTextIndex/okascore.c']),
 
-    #ZODB
-    Extension(name = 'persistent.cPersistence',
-              include_dirs = ['persistent'],
-              sources= ['persistent/cPersistence.c',
-                        'persistent/ring.c'],
-              depends = ['persistent/cPersistence.h',
-                         'persistent/ring.h',
-                         'persistent/ring.c']
-              ),
-    Extension(name = 'Persistence._Persistence',
-              include_dirs = ['.', 'persistent', 'ExtensionClass'],
-              sources = ['Persistence/_Persistence.c'],
-              depends = ['persistent/cPersistence.h',
-                         'ExtensionClass/ExtensionClass.h']
-              ),
-    Extension(name = 'persistent.cPickleCache',
-              include_dirs = ['persistent'],
-              sources= ['persistent/cPickleCache.c',
-                        'persistent/ring.c'],
-              depends = ['persistent/cPersistence.h',
-                         'persistent/ring.h',
-                         'persistent/ring.c']
-              ),
-    Extension(name = 'persistent.TimeStamp',
-              sources= ['persistent/TimeStamp.c']
-              ),
-    Extension(name = 'ZODB.winlock',
-              sources = ['ZODB/winlock.c']
-              ),
-
     #zope
     Extension("zope.proxy._zope_proxy_proxy",
               ["zope/proxy/_zope_proxy_proxy.c"],
@@ -269,6 +268,13 @@
                  "zope/proxy/_zope_proxy_proxy.c",
                  ]),
 
+    # ZODB-related
+    Extension(name = 'Persistence._Persistence',
+              include_dirs = ['.', 'persistent', 'ExtensionClass'],
+              sources = ['Persistence/_Persistence.c'],
+              depends = ['persistent/cPersistence.h',
+                         'ExtensionClass/ExtensionClass.h']
+              ),
     ]
 
 setup(name='zopelib',
@@ -279,10 +285,38 @@
       author="Zope Corporation and Contributors",
       author_email="zope at zope.org",
       url="http://www.zope.org",
-      ext_modules = ext_modules,
+      ext_modules = zope_ext_modules,
       license="ZPL 2.0",
-      packages=find_packages(),
+      # exclude ZODB packages
+      packages=find_packages(exclude=[
+                  "BTrees",
+                  "BTrees.*",
+                  "ZConfig",
+                  "ZConfig.*",
+                  "ZODB",
+                  "ZODB.*",
+                  "persistent",
+                  "persistent.*",
+                  "transaction",
+                  "transaction.*",
+                  "zope.interface",
+                  "zope.interface.*",
+                  "zope.proxy",
+                  "zope.proxy.*",
+                  "zope.testing",
+                  "zope.testing.*",
+                  "ThreadedAsync",
+                  "ThreadedAsync.*",
+                  "ZEO",
+                  "ZEO.*",
+                  "ZopeUndo",
+                  "ZopeUndo.*",
+                  "zdaemon",
+                  "zdaemon.*",
+                ]),
       include_package_data=True,
+      dependency_links=["http://dist.repoze.org/"],
+      install_requires=["ZODB3==3.7.2"],
       namespace_packages=['Products'],
       zip_safe=False,
       distclass=MyDistribution,

Added: repoze.decsec/trunk/CHANGES.txt
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/CHANGES.txt	Thu Feb 21 03:06:07 2008
@@ -0,0 +1 @@
+

Added: repoze.decsec/trunk/COPYRIGHT.txt
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/COPYRIGHT.txt	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,3 @@
+Copyright (c) 2007 Agendaless Consulting and Contributors.
+(http://www.agendaless.com), All Rights Reserved
+

Added: repoze.decsec/trunk/LICENSE.txt
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/LICENSE.txt	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,41 @@
+License
+
+  A copyright notice accompanies this license document that identifies
+  the copyright holders.
+
+  Redistribution and use in source and binary forms, with or without
+  modification, are permitted provided that the following conditions are
+  met:
+
+  1.  Redistributions in source code must retain the accompanying
+      copyright notice, this list of conditions, and the following
+      disclaimer.
+
+  2.  Redistributions in binary form must reproduce the accompanying
+      copyright notice, this list of conditions, and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+
+  3.  Names of the copyright holders must not be used to endorse or
+      promote products derived from this software without prior
+      written permission from the copyright holders.
+
+  4.  If any files are modified, you must cause the modified files to
+      carry prominent notices stating that you changed the files and
+      the date of any change.
+
+  Disclaimer
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND
+    ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+    TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    HOLDERS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+    EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
+    TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+    ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+    TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+    THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+    SUCH DAMAGE.
+

Added: repoze.decsec/trunk/README.txt
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/README.txt	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,15 @@
+repoze.decsec README
+=====================
+
+Declarative ACL-based security for WSGI applications.
+
+Installation
+------------
+
+Install using setuptools, e.g. (within a virtualenv)::
+
+ $ bin/easy_install -d http://dist.repoze.org/ repoze.decsec
+
+Configuration
+-------------
+

Added: repoze.decsec/trunk/TODO.txt
==============================================================================

Added: repoze.decsec/trunk/ez_setup.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/ez_setup.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,234 @@
+#!python
+"""Bootstrap setuptools installation
+
+If you want to use setuptools in your package's setup.py, just include this
+file in the same directory with it, and add this to the top of your setup.py::
+
+    from ez_setup import use_setuptools
+    use_setuptools()
+
+If you want to require a specific version of setuptools, set a download
+mirror, or use an alternate download directory, you can do so by supplying
+the appropriate options to ``use_setuptools()``.
+
+This file can also be run as a script to install or upgrade setuptools.
+"""
+import sys
+DEFAULT_VERSION = "0.6c7"
+DEFAULT_URL     = "http://pypi.python.org/packages/%s/s/setuptools/" % sys.version[:3]
+
+md5_data = {
+    'setuptools-0.6b1-py2.3.egg': '8822caf901250d848b996b7f25c6e6ca',
+    'setuptools-0.6b1-py2.4.egg': 'b79a8a403e4502fbb85ee3f1941735cb',
+    'setuptools-0.6b2-py2.3.egg': '5657759d8a6d8fc44070a9d07272d99b',
+    'setuptools-0.6b2-py2.4.egg': '4996a8d169d2be661fa32a6e52e4f82a',
+    'setuptools-0.6b3-py2.3.egg': 'bb31c0fc7399a63579975cad9f5a0618',
+    'setuptools-0.6b3-py2.4.egg': '38a8c6b3d6ecd22247f179f7da669fac',
+    'setuptools-0.6b4-py2.3.egg': '62045a24ed4e1ebc77fe039aa4e6f7e5',
+    'setuptools-0.6b4-py2.4.egg': '4cb2a185d228dacffb2d17f103b3b1c4',
+    'setuptools-0.6c1-py2.3.egg': 'b3f2b5539d65cb7f74ad79127f1a908c',
+    'setuptools-0.6c1-py2.4.egg': 'b45adeda0667d2d2ffe14009364f2a4b',
+    'setuptools-0.6c2-py2.3.egg': 'f0064bf6aa2b7d0f3ba0b43f20817c27',
+    'setuptools-0.6c2-py2.4.egg': '616192eec35f47e8ea16cd6a122b7277',
+    'setuptools-0.6c3-py2.3.egg': 'f181fa125dfe85a259c9cd6f1d7b78fa',
+    'setuptools-0.6c3-py2.4.egg': 'e0ed74682c998bfb73bf803a50e7b71e',
+    'setuptools-0.6c3-py2.5.egg': 'abef16fdd61955514841c7c6bd98965e',
+    'setuptools-0.6c4-py2.3.egg': 'b0b9131acab32022bfac7f44c5d7971f',
+    'setuptools-0.6c4-py2.4.egg': '2a1f9656d4fbf3c97bf946c0a124e6e2',
+    'setuptools-0.6c4-py2.5.egg': '8f5a052e32cdb9c72bcf4b5526f28afc',
+    'setuptools-0.6c5-py2.3.egg': 'ee9fd80965da04f2f3e6b3576e9d8167',
+    'setuptools-0.6c5-py2.4.egg': 'afe2adf1c01701ee841761f5bcd8aa64',
+    'setuptools-0.6c5-py2.5.egg': 'a8d3f61494ccaa8714dfed37bccd3d5d',
+    'setuptools-0.6c6-py2.3.egg': '35686b78116a668847237b69d549ec20',
+    'setuptools-0.6c6-py2.4.egg': '3c56af57be3225019260a644430065ab',
+    'setuptools-0.6c6-py2.5.egg': 'b2f8a7520709a5b34f80946de5f02f53',
+    'setuptools-0.6c7-py2.3.egg': '209fdf9adc3a615e5115b725658e13e2',
+    'setuptools-0.6c7-py2.4.egg': '5a8f954807d46a0fb67cf1f26c55a82e',
+    'setuptools-0.6c7-py2.5.egg': '45d2ad28f9750e7434111fde831e8372',
+}
+
+import sys, os
+
+def _validate_md5(egg_name, data):
+    if egg_name in md5_data:
+        from md5 import md5
+        digest = md5(data).hexdigest()
+        if digest != md5_data[egg_name]:
+            print >>sys.stderr, (
+                "md5 validation of %s failed!  (Possible download problem?)"
+                % egg_name
+            )
+            sys.exit(2)
+    return data
+
+
+def use_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    download_delay=15
+):
+    """Automatically find/download setuptools and make it available on sys.path
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end with
+    a '/').  `to_dir` is the directory where setuptools will be downloaded, if
+    it is not already available.  If `download_delay` is specified, it should
+    be the number of seconds that will be paused before initiating a download,
+    should one be required.  If an older version of setuptools is installed,
+    this routine will print a message to ``sys.stderr`` and raise SystemExit in
+    an attempt to abort the calling script.
+    """
+    try:
+        import setuptools
+        if setuptools.__version__ == '0.0.1':
+            print >>sys.stderr, (
+            "You have an obsolete version of setuptools installed.  Please\n"
+            "remove it from your system entirely before rerunning this script."
+            )
+            sys.exit(2)
+    except ImportError:
+        egg = download_setuptools(version, download_base, to_dir, download_delay)
+        sys.path.insert(0, egg)
+        import setuptools; setuptools.bootstrap_install_from = egg
+
+    import pkg_resources
+    try:
+        pkg_resources.require("setuptools>="+version)
+
+    except pkg_resources.VersionConflict, e:
+        # XXX could we install in a subprocess here?
+        print >>sys.stderr, (
+            "The required version of setuptools (>=%s) is not available, and\n"
+            "can't be installed while this script is running. Please install\n"
+            " a more recent version first.\n\n(Currently using %r)"
+        ) % (version, e.args[0])
+        sys.exit(2)
+
+def download_setuptools(
+    version=DEFAULT_VERSION, download_base=DEFAULT_URL, to_dir=os.curdir,
+    delay = 15
+):
+    """Download setuptools from a specified location and return its filename
+
+    `version` should be a valid setuptools version number that is available
+    as an egg for download under the `download_base` URL (which should end
+    with a '/'). `to_dir` is the directory where the egg will be downloaded.
+    `delay` is the number of seconds to pause before an actual download attempt.
+    """
+    import urllib2, shutil
+    egg_name = "setuptools-%s-py%s.egg" % (version,sys.version[:3])
+    url = download_base + egg_name
+    saveto = os.path.join(to_dir, egg_name)
+    src = dst = None
+    if not os.path.exists(saveto):  # Avoid repeated downloads
+        try:
+            from distutils import log
+            if delay:
+                log.warn("""
+---------------------------------------------------------------------------
+This script requires setuptools version %s to run (even to display
+help).  I will attempt to download it for you (from
+%s), but
+you may need to enable firewall access for this script first.
+I will start the download in %d seconds.
+
+(Note: if this machine does not have network access, please obtain the file
+
+   %s
+
+and place it in this directory before rerunning this script.)
+---------------------------------------------------------------------------""",
+                    version, download_base, delay, url
+                ); from time import sleep; sleep(delay)
+            log.warn("Downloading %s", url)
+            src = urllib2.urlopen(url)
+            # Read/write all in one block, so we don't create a corrupt file
+            # if the download is interrupted.
+            data = _validate_md5(egg_name, src.read())
+            dst = open(saveto,"wb"); dst.write(data)
+        finally:
+            if src: src.close()
+            if dst: dst.close()
+    return os.path.realpath(saveto)
+
+def main(argv, version=DEFAULT_VERSION):
+    """Install or upgrade setuptools and EasyInstall"""
+
+    try:
+        import setuptools
+    except ImportError:
+        egg = None
+        try:
+            egg = download_setuptools(version, delay=0)
+            sys.path.insert(0,egg)
+            from setuptools.command.easy_install import main
+            return main(list(argv)+[egg])   # we're done here
+        finally:
+            if egg and os.path.exists(egg):
+                os.unlink(egg)
+    else:
+        if setuptools.__version__ == '0.0.1':
+            # tell the user to uninstall obsolete version
+            use_setuptools(version)
+
+    req = "setuptools>="+version
+    import pkg_resources
+    try:
+        pkg_resources.require(req)
+    except pkg_resources.VersionConflict:
+        try:
+            from setuptools.command.easy_install import main
+        except ImportError:
+            from easy_install import main
+        main(list(argv)+[download_setuptools(delay=0)])
+        sys.exit(0) # try to force an exit
+    else:
+        if argv:
+            from setuptools.command.easy_install import main
+            main(argv)
+        else:
+            print "Setuptools version",version,"or greater has been installed."
+            print '(Run "ez_setup.py -U setuptools" to reinstall or upgrade.)'
+
+
+
+def update_md5(filenames):
+    """Update our built-in md5 registry"""
+
+    import re
+    from md5 import md5
+
+    for name in filenames:
+        base = os.path.basename(name)
+        f = open(name,'rb')
+        md5_data[base] = md5(f.read()).hexdigest()
+        f.close()
+
+    data = ["    %r: %r,\n" % it for it in md5_data.items()]
+    data.sort()
+    repl = "".join(data)
+
+    import inspect
+    srcfile = inspect.getsourcefile(sys.modules[__name__])
+    f = open(srcfile, 'rb'); src = f.read(); f.close()
+
+    match = re.search("\nmd5_data = {\n([^}]+)}", src)
+    if not match:
+        print >>sys.stderr, "Internal error!"
+        sys.exit(2)
+
+    src = src[:match.start(1)] + repl + src[match.end(1):]
+    f = open(srcfile,'w')
+    f.write(src)
+    f.close()
+
+
+if __name__=='__main__':
+    if len(sys.argv)>2 and sys.argv[1]=='--md5update':
+        update_md5(sys.argv[2:])
+    else:
+        main(sys.argv[1:])
+
+
+
+
+

Added: repoze.decsec/trunk/repoze/__init__.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/__init__.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,2 @@
+# repoze package
+__import__('pkg_resources').declare_namespace(__name__)

Added: repoze.decsec/trunk/repoze/decsec/__init__.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/decsec/__init__.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1 @@
+# a package

Added: repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/decsec/etc/sample-config.ini	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,11 @@
+[decsec]
+remote_user_key=REMOTE_USER
+deny_exception=Paste.httpexceptions:Unauthorized
+log_filename=%(here)s/decsec.log
+log_level=info
+allow_on_nomatch=false
+initialize_hook=decsec.sample:initialize
+get_user_hook=decsec.sample:get_user
+principals_for_user_hook=decsec.sample:principals_for_user
+acl_for_request_hook=decsec.sample:acl_for_request
+protected_by_hook=decsec.sample:protected_by

Added: repoze.decsec/trunk/repoze/decsec/interfaces.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/decsec/interfaces.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,2 @@
+Everyone = 'Everyone'
+Authenticated = 'Authenticated'

Added: repoze.decsec/trunk/repoze/decsec/middleware.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/decsec/middleware.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,142 @@
+from paste import httpexceptions
+import logging
+
+from repoze.decsec.interfaces import Everyone
+from repoze.decsec.interfaces import Authenticated
+
+class DecsecMiddleware(object):
+    def __init__(self,
+                 app,
+                 get_user,
+                 principals_for_user,
+                 acl_for_request,
+                 initialize = None,
+                 log_filename='decsec.log',
+                 log_level=logging.INFO,
+                 allow_on_nomatch = False,
+                 remote_user_key='REMOTE_USER',
+                 deny_exception=httpexceptions.HTTPUnauthorized,
+                ):
+        self.app = app
+        self.remote_user_key = remote_user_key
+        self.deny_exception = deny_exception
+        self.log_filename = log_filename
+        self.log_level = log_level
+        self.allow_on_nomatch = allow_on_nomatch
+        self.get_user = get_user
+        self.principals_for_user = principals_for_user
+        self.acl_for_request = acl_for_request
+        if initialize:
+            initialize(self)
+
+    def __call__(self, environ, start_response):
+        allowed, reason = self.allowed(environ)
+        if not allowed:
+            raise self.deny_exception(reason)
+        self.app(environ, start_response)
+
+    def allowed(self, environ):
+        principals = [Everyone]
+        remote_user = environ.get(self.remote_user_key)
+        if remote_user:
+            principals.append(Authenticated)
+        userid = self.get_user(remote_user)
+        user_principals = self.principals_for_user(userid)
+        principals.extend(user_principals)
+        protected_by = self.protected_by(environ)
+        acl = self.acl_for_request(environ)
+        if not self.acl_allows(acl, protected_by, principals):
+            reason = u'%s %s' % (acl, principals)
+            return False, reason
+        return True, None
+
+    def acl_allows(self, acl, protected_by, principals):
+        for ace in acl:
+            for principal in principals:
+                if ace['principal'] == principal:
+                    permission = ace['permission']
+                    if isinstance(permission, basestring):
+                        permissions = [permission]
+                    else:
+                        permissions = flatten(permission)
+                    if protected_by in permissions:
+                        action = ace['action']
+                        if action == 'allow':
+                            return True
+                        # if it's a deny
+                        return False
+        return False
+
+def _resolve(dotted_or_ep):
+    """ Resolve a dotted name or setuptools entry point to a callable.
+    """
+    from pkg_resources import EntryPoint
+    return EntryPoint.parse('x=%s' % dotted_or_ep).load(False)
+
+def flatten(x):
+    """flatten(sequence) -> list
+
+    Returns a single, flat list which contains all elements retrieved
+    from the sequence and all recursively contained sub-sequences
+    (iterables).
+
+    Examples:
+    >>> [1, 2, [3,4], (5,6)]
+    [1, 2, [3, 4], (5, 6)]
+    >>> flatten([[[1,2,3], (42,None)], [4,5], [6], 7, MyVector(8,9,10)])
+    [1, 2, 3, 42, None, 4, 5, 6, 7, 8, 9, 10]"""
+
+    result = []
+    for el in x:
+        if hasattr(el, "__iter__") and not isinstance(el, basestring):
+            result.extend(flatten(el))
+        else:
+            result.append(el)
+    return result
+
+def make_middleware(app,
+                    global_conf,
+                    get_user_hook=None,
+                    principals_for_user_hook=None,
+                    acl_for_request_hook=None,
+                    protected_by_hook=None,
+                    initialize_hook=None,
+                    log_filename='decsec.log',
+                    log_level='info',
+                    allow_on_nomatch='false',
+                    remote_user_key='REMOTE_USER',
+                    deny_exception='paste.httpexceptions:HTTPUnauthorized',
+                    ):
+    if get_user_hook is None:
+        raise ValueError('get_user_hook must be specified')
+    if principals_for_user_hook is None:
+        raise ValueError('principals_for_user_hook must be specified')
+    if acl_for_request_hook is None:
+        raise ValueError('acl_for_request_hook must be specified')
+    if protected_by_hook is None:
+        raise ValueError('protected_by_hook must be specified')
+    if allow_on_nomatch.lower().startswith('true'):
+        allow_on_nomatch = True
+    else:
+        allow_on_nomatch = False
+    deny_exception = _resolve(deny_exception)
+    get_user = _resolve(get_user_hook)
+    principals_for_user = _resolve(principals_for_user_hook)
+    acl_for_request = _resolve(acl_for_request_hook)
+    protected_by = _resolve(protected_by_hook)
+    initialize = None
+    if initialize_hook is not None:
+        initialize = _resolve(initialize_hook)
+    log_level = getattr(logging, log_level.upper())
+    return DecsecMiddleware(app,
+                            get_user=get_user,
+                            principals_for_user=principals_for_user,
+                            acl_for_request = acl_for_request,
+                            protected_by = protected_by,
+                            initialize = initialize,
+                            log_filename='decsec.log',
+                            log_level=logging.INFO,
+                            allow_on_nomatch = False,
+                            remote_user_key='REMOTE_USER',
+                            deny_exception=httpexceptions.HTTPUnauthorized,
+                            )

Added: repoze.decsec/trunk/repoze/decsec/sample.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/decsec/sample.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,65 @@
+from repoze.decsec.interfaces import Everyone
+from repoze.decsec.interfaces import Authenticated
+
+def initialize(global_conf, *kw):
+    """ Hook point: hook.initialize """
+    pass
+
+users = {
+    'chrism at agendaless.com':'chris',
+    'paul at agendaless.com':'paul',
+    'tseaver at agendaless.com':'tres',
+    }
+
+groups = {
+    'cynics':['chris'],
+    'agendaless':['chris', 'paul', 'tres'],
+    }
+
+class Place:
+    def __init__(self, path, protected_by, acl):
+        self.path = path
+        self.protected_by = protected_by
+        self.acl = acl
+
+permissions = [ 'read', 'write' ]
+any = permissions
+
+def ACE(action, principal, permission):
+    return {'action':action, 'principal':principal, 'permission':permission}
+
+allow_cynics_to_any = ACE('allow', 'cynics', any)
+allow_agendaless_to_any = ACE('allow', 'agendaless',  any)
+allow_authenticated_to_write = ACE('allow', Authenticated, 'write')
+allow_everyone_to_read = ACE('allow', Everyone, 'read')
+
+places = [
+    Place('/cynics', 'write', [allow_cynics_to_any]),
+    Place('/authenticated', 'write', [allow_authenticated_to_write]),
+    Place('/', 'read', [allow_agendaless_to_any, allow_everyone_to_read]),
+    ]
+    
+def get_user(self, remote_user_value):
+    return users.get(remote_user_value)
+
+def principals_for_user(userid):
+    result = [userid]
+    for groupname, members in groups.items():
+        if userid in members:
+            result.append(groupname)
+    return result
+
+def acl_for_request(environ):
+    path = environ['PATH_INFO']
+    for place in places:
+        if path.startswith(place.path):
+            return place.acl
+    return [{'action':'deny', 'principal':Everyone, 'permission':any}]
+
+def protected_by(environ):
+    path = environ['PATH_INFO']
+    for place in places:
+        if path.startswith(place.path):
+            return place.protected_by
+        
+    

Added: repoze.decsec/trunk/repoze/decsec/tests.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/repoze/decsec/tests.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,19 @@
+import unittest
+
+class TestMiddleware(unittest.TestCase):
+    def _getTargetClass(self):
+        from repoze.decsec.middleware import DecsecMiddleware
+        return DecsecMiddleware
+    
+    def _makeOne(self, *arg, **kw):
+        klass = self._getTargetClass()
+        return klass(*arg, **kw)
+
+
+    def test_ctor(self):
+        app = DummyApp()
+        inst = self._makeOne(app, None, None, None, None)
+        self.assertEqual(inst.app, app)
+
+class DummyApp:
+    pass

Added: repoze.decsec/trunk/setup.py
==============================================================================
--- (empty file)
+++ repoze.decsec/trunk/setup.py	Thu Feb 21 03:06:07 2008
@@ -0,0 +1,58 @@
+##############################################################################
+#
+# Copyright (c) 2007 Agendaless Consulting and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the BSD-like license at
+# http://www.repoze.org/LICENSE.txt.  A copy of the license 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
+#
+##############################################################################
+
+__version__ = '0.1'
+
+import os
+
+from ez_setup import use_setuptools
+use_setuptools()
+
+from setuptools import setup, find_packages
+
+here = os.path.abspath(os.path.dirname(__file__))
+README = open(os.path.join(here, 'README.txt')).read()
+
+setup(name='repoze.decsec',
+      version=__version__,
+      description='Declarative ACL-based security for WSGI applications',
+      long_description=README,
+      classifiers=[
+        "Development Status :: 1 - Planning",
+        "Intended Audience :: Developers",
+        "Programming Language :: Python",
+        "Topic :: Internet :: WWW/HTTP",
+        "Topic :: Internet :: WWW/HTTP :: Dynamic Content",
+        "Topic :: Internet :: WWW/HTTP :: WSGI",
+        "Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
+        ],
+      keywords='web application server wsgi zope',
+      author="Agendaless Consulting",
+      author_email="repoze-dev at lists.repoze.org",
+      dependency_links=['http://dist.repoze.org'],
+      url="http://www.repoze.org",
+      license="BSD-derived (http://www.repoze.org/LICENSE.txt)",
+      packages=find_packages(),
+      include_package_data=True,
+      namespace_packages=['repoze'],
+      zip_safe=False,
+      tests_require = ['Paste'],
+      install_requires=['Paste'],
+      test_suite="repoze.decsec.tests",
+      entry_points = """\
+      [paste.filter_app_factory]
+      profile = repoze.decsec.middleware:make_middleware
+      """
+      )
+

Modified: repoze.errorlog/trunk/CHANGES.txt
==============================================================================
--- repoze.errorlog/trunk/CHANGES.txt	(original)
+++ repoze.errorlog/trunk/CHANGES.txt	Thu Feb 21 03:06:07 2008
@@ -1,3 +1,6 @@
+0.5
+
+  - Depend on elementree 1.2.6 explicitly.
 0.4
 
   - Add 'ignore' feature to configuration.  A value consisting of

Modified: repoze.errorlog/trunk/setup.py
==============================================================================
--- repoze.errorlog/trunk/setup.py	(original)
+++ repoze.errorlog/trunk/setup.py	Thu Feb 21 03:06:07 2008
@@ -12,7 +12,7 @@
 #
 ##############################################################################
 
-__version__ = '0.4'
+__version__ = '0.5'
 
 import os
 
@@ -50,7 +50,7 @@
       tests_require = [],
       install_requires=[
            'meld3 >= 0.6.3',
-           'elementtree >= 1.2.6',
+           'elementtree >= 1.2.6, < 1.2.7',
            'Paste >= 1.5',
            ],
       test_suite="repoze.errorlog.tests",

Modified: repoze.project/trunk/CHANGES.txt
==============================================================================
--- repoze.project/trunk/CHANGES.txt	(original)
+++ repoze.project/trunk/CHANGES.txt	Thu Feb 21 03:06:07 2008
@@ -1,5 +1,10 @@
 repoze.project Changelog
 
+  repoze-project 0.0.5 (2007-12-14)
+
+    - Allow users to specify --index-url on the commandline to specify
+      a different index rather than only being able to specify pypi:
+
   repoze-project 0.0.4 (2007-10-24)
 
     - Depend on virtualenv >= 0.9.1 (so people who use Mac framework

Modified: repoze.project/trunk/repoze/project/bootstrap.py
==============================================================================
--- repoze.project/trunk/repoze/project/bootstrap.py	(original)
+++ repoze.project/trunk/repoze/project/bootstrap.py	Thu Feb 21 03:06:07 2008
@@ -19,6 +19,8 @@
 
 EXTEND_PARSER = """\
 
+REPOZE_INDEX = 'http://dist.repoze.org/simple'
+
 def extend_parser(parser):
 
     parser.add_option(
@@ -36,6 +38,14 @@
         help="Do not initialize the environment from the "
              "'repoze.project.initialize' entry point(s) defined in "
              "the first egg.")
+
+    parser.add_option(
+        '-i', '--index-url',
+        action='store',
+        dest='index',
+        default=REPOZE_INDEX,
+        help='URL of package index')
+
 """
 
 AI_PROLOG = """\
@@ -59,13 +69,17 @@
     else:
         args.append('--no-initialize-environment')
 
+    args.extend(['--index-url=' + options.index])
+
     args.extend([%s])
 
-    subprocess.call([join(home_dir, 'bin', 'repozeproject')] + args)
+    command = join(home_dir, 'bin', 'repozeproject')
+    print 'command', command
+    print 'args', args
+    subprocess.call([command] + args)
 """
 
 def main():
-    
     parser = optparse.OptionParser(
         usage="%prog [OPTIONS] app_egg_name [other_egg_name]*")
 
@@ -74,7 +88,16 @@
         action='store',
         dest='path',
         default=None,
-        help="Specify the path in which to build the application")
+        help=('Specify the path in which to build the application '
+              '(default: use sys.prefix)')
+        )
+
+    parser.add_option(
+        '-i', '--index-url',
+        action='store',
+        dest='index',
+        default=REPOZE_INDEX,
+        help='URL of package index')
 
     parser.add_option(
         '--site-packages',
@@ -112,7 +135,7 @@
     if len(args) == 0:
         parser.print_help(sys.stderr)
         sys.exit(1)
-    
+
     if options.path is not None:  # don't do it in place
 
         # we want the virtualenv to have the same version of repoze.project
@@ -157,19 +180,16 @@
 
     else: # do local install
 
-        for arg in args:
-            index = REPOZE_INDEX
-
-            if arg.startswith('pypi:'):
-                arg = arg[len('pypi:'):]
-                index = PYPI_INDEX
+        index = options.index
 
+        for arg in args:
             sub_args = [EASY_INSTALL,
                         '--index-url',
                         index,
                         arg,
-                       ]
-            virtualenv.call_subprocess(sub_args)
+                        ]
+
+        virtualenv.call_subprocess(sub_args)
 
         if options.initialize_environment:
             dist = args[0]

Modified: repoze.project/trunk/setup.py
==============================================================================
--- repoze.project/trunk/setup.py	(original)
+++ repoze.project/trunk/setup.py	Thu Feb 21 03:06:07 2008
@@ -12,7 +12,7 @@
 #
 ##############################################################################
 
-__version__ = '0.0.4'
+__version__ = '0.0.5'
 
 from ez_setup import use_setuptools
 use_setuptools()


More information about the Repoze-checkins mailing list