* looking for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-557 to compare with
* build pristine tree for arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-557
* from pristine cache: arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-556
* patching for revision: arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-557
* comparing to arch@arch.thinkmo.de--2003-archives/moin--main--1.3--patch-557
M  MoinMoin/PageEditor.py
M  MoinMoin/multiconfig.py
M  MoinMoin/security.py
M  MoinMoin/wikiacl.py

* modified files

--- orig/MoinMoin/PageEditor.py
+++ mod/MoinMoin/PageEditor.py
@@ -904,8 +904,8 @@
             # rights. This is a good place to update acl cache - instead
             # of wating for next request.
             acl = self.getACL(self.request)
-            if (not acl.may(self.request, self.request.user.name, "admin") and
-                parseACL(self.request, newtext) != acl and
+            if (not self.request.user.may.admin(self.page_name) and
+                parseACL(self.request, newtext).acl != acl.acl and
                 action != "SAVE/REVERT"):
                 msg = _("You can't change ACLs on this page since you have no admin rights on it!")
                 raise self.NoAdmin, msg


--- orig/MoinMoin/multiconfig.py
+++ mod/MoinMoin/multiconfig.py
@@ -151,6 +151,7 @@
     FIXME: update according to MoinMoin:UpdateConfiguration
     """    
     acl_enabled = 0
+    acl_hierarchic = 0
     # All acl_right lines must use unicode!
     acl_rights_default = u"Trusted:read,write,delete,revert Known:read,write,delete,revert All:read,write"
     acl_rights_before = u""
@@ -321,6 +322,13 @@
         # e.g u'%(page_front_page)s' % self
         self.navi_bar = [elem % self for elem in self.navi_bar]
 
+        # precompile acl strings
+        if self.acl_enabled:
+            from wikiacl import AccessControlList
+            self.acl_default = AccessControlList(None, [self.acl_rights_default], cfg=self)
+            self.acl_before = AccessControlList(None, [self.acl_rights_before], cfg=self)
+            self.acl_after = AccessControlList(None, [self.acl_rights_after], cfg=self)
+
     def _config_check(self):
         """ Check namespace and warn about unknown names
         


--- orig/MoinMoin/security.py
+++ mod/MoinMoin/security.py
@@ -22,8 +22,21 @@
 class Permissions:
     """ Basic interface for user permissions and system policy.
 
-        Note that you still need to allow some of the related actions, this
-        just controls their behaviour, not their activation.
+    Note that you still need to allow some of the related actions, this
+    just controls their behavior, not their activation.
+
+    When sub classing this class, you must extend the class methods, not
+    replace them, or you might break the acl in the wiki. Correct sub
+    classing look like this:
+
+    def read(self, pagename):
+        # Your special security rule
+        if somehting:
+            return false
+
+        # Do not return True or you break acl!
+        # This call will use the default acl rules
+        return Permissions.read(pagename)
     """
 
     def __init__(self, user):
@@ -32,27 +45,102 @@
         from MoinMoin.Page import Page
         self.Page = Page
         self.name = user.name
+        self.user = user
         self.request = user._request
 
     def save(self, editor, newtext, rev, **kw):
         """ Check whether user may save a page.
 
-            `editor` is the PageEditor instance, the other arguments are
-            those of the `PageEditor.saveText` method.
+        Default implementation ignore newtext, rev and kw, and just try
+        to get permission for write.
+
+        @param editor: PageEditor instance.
+        @param newtext: new page text, you can enable of disable saving according
+            to the content of the text, e.g. prevent link spam.
+        @param rev: new revision number? XXX
+        @param kw: XXX
+        @rtype: bool
+        @return: True if you can save or False
         """
         return self.write(editor.page_name)
 
     def __getattr__(self, attr):
-        """ if attr is one of the rights in acl_rights_valid, then return a
-            checking function for it. Else raise an error.
+        """ Shortcut to export getPermission function for all known acl rights
+        
+        if attr is one of the rights in acl_rights_valid, then return a
+        checking function for it. Else use normal getattr().
+
+        @param attr: one of acl known rights as defined in acl_rights_valid
+        @rtype: function
+        @return: checking function for that right, accepting a pagename
         """
-        request = self.request
-        Page = self.Page
-        if attr in request.cfg.acl_rights_valid:
-            return lambda pagename, Page=Page, request=request, attr=attr: Page(request, pagename).getACL(request).may(request, self.name, attr)
+        if attr in self.request.cfg.acl_rights_valid:
+            self._right = attr
+            return self._getPermission
         else:
             raise AttributeError, attr
         
+    def _getPermission(self, pagename):
+        """ Get permission by transversing page hierarchy
+ 
+        If cfg.acl_hierarchic, we check each page in the hierarchy. If all
+        pages agree, we have permission. If one page in the hierarchy
+        does not agree, we don't. Otherwise, only pagename is checked.
+
+        This method should not be called by users, use __getattr__
+        instead. If you want to get permission for delete, try:
+        instance.delete(pagename).
+
+        @param pagename: pagename to get permission from
+        @rtype: bool
+        @return: True if you have permission or False
+        """
+        from MoinMoin.Page import Page
+
+        # Use page hierarchy or only pagename, according to wiki config.
+        if self.request.cfg.acl_hierarchic:
+            pages = pagename.split('/')
+        else:
+            pages = [pagename]
+
+        open('/tmp/acl.log','a').write('may(%s): pages %s\n'%(self._right, pages,))
+        # check before
+        allowed = self.request.cfg.acl_before.may(self.request, self.user.name, self._right)
+        if allowed is not None:
+            open('/tmp/acl.log','a').write('may: before returns %s\n'%(allowed,))
+            return allowed
+        
+        # Get permission
+        some_acl = False
+        for i in range(len(pages), 0, -1):
+            # Create the next pagename in the hierarchy
+            # starting with the leave, going to the root
+            name = '/'.join(pages[:i])
+            # Get page acl and ask for permission
+            acl = Page(self.request, name).getACL(self.request)
+            open('/tmp/acl.log','a').write('may: checking acl of %s (%s)\n'%(name, `acl.acl`))
+            if acl.acl:
+                some_acl = True
+                allowed = acl.may(self.request, self.user.name, self._right)
+                if allowed is not None:
+                    open('/tmp/acl.log','a').write('may: acl of %s (%s) returns %s\n'%(name, `acl.acl`, allowed,))
+                    return allowed
+        if not some_acl:
+            allowed = self.request.cfg.acl_default.may(self.request, self.user.name, self._right)
+            if allowed is not None:
+                open('/tmp/acl.log','a').write('may: default returns %s\n'%(allowed,))
+                return allowed
+
+        # check after
+        allowed = self.request.cfg.acl_after.may(self.request, self.user.name, self._right)
+        if allowed is not None:
+            open('/tmp/acl.log','a').write('may: after returns %s\n'%(allowed,))
+            return allowed
+
+        # sane default permission, if you want True,
+        # just put 'All:read' (or more) into your acl_rights_after
+        return False
+
 
 # make an alias for the default policy
 Default = Permissions


--- orig/MoinMoin/wikiacl.py
+++ mod/MoinMoin/wikiacl.py
@@ -113,33 +113,24 @@
 
     special_users = ["All", "Known", "Trusted"]
 
-    def __init__(self, request, lines=[]):
+    def __init__(self, request, lines=[], cfg=None):
         """Initialize an ACL, starting from <nothing>.
         """
-        self.setLines(request.cfg, lines)
+        if cfg is not None:
+            self.setLines(cfg, lines)
+        else:
+            self.setLines(request.cfg, lines)
 
     def setLines(self, cfg, lines=[]):
         self.clean()
-        self.addBefore(cfg)
-        if not lines:
-            self.addDefault(cfg)
-        else:
-            for line in lines:
-                self.addLine(cfg, line)
-        self.addAfter(cfg)
+        for line in lines:
+            self.addLine(cfg, line)
 
     def clean(self):
         self.acl = [] # [ ('User', {"read": 0, ...}), ... ]
         self.acl_lines = []
         self._is_group = {}
 
-    def addBefore(self, cfg):
-        self.addLine(cfg, cfg.acl_rights_before, remember=0)
-    def addDefault(self, cfg):
-        self.addLine(cfg, cfg.acl_rights_default, remember=0)
-    def addAfter(self, cfg):
-        self.addLine(cfg, cfg.acl_rights_after, remember=0)
-
     def addLine(self, cfg, aclstring, remember=1):
         """ Add another ACL line
 
@@ -182,7 +173,7 @@
                         rightsdict[right] = (right in rights)
                 self.acl.append((entry, rightsdict))
 
-    def may(self, request, name, dowhat):
+    def may(self, request, name, dowhat, recurse=False):
         """May <name> <dowhat>?
            Returns boolean answer.
         """
@@ -210,7 +201,7 @@
                 allowed = rightsdict.get(dowhat)
             if allowed is not None:
                 return allowed
-        return 0
+        return allowed
 
     def getString(self, b='#acl ', e='\n'):
         """print the acl strings we were fed with"""



