changeset 747:e178ada80ead

moved wikiacl.py to security/__init__.py
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Tue, 06 Jun 2006 22:52:53 +0200
parents 0d3e9b79dde3
children bff32ecb8a57
files MoinMoin/Page.py MoinMoin/PageEditor.py MoinMoin/_tests/test_wikiacl.py MoinMoin/i18n/POTFILES.in MoinMoin/security/__init__.py MoinMoin/wikiacl.py docs/CHANGES
diffstat 7 files changed, 395 insertions(+), 411 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/Page.py	Tue Jun 06 21:39:16 2006 +0200
+++ b/MoinMoin/Page.py	Tue Jun 06 22:52:53 2006 +0200
@@ -1603,10 +1603,9 @@
         Return cached ACL or invoke parseACL and update the cache.
 
         @param request: the request object
-        @rtype: MoinMoin.wikiacl.AccessControlList
+        @rtype: MoinMoin.security.AccessControlList
         @return: ACL of this page
         """
-        import wikiacl
         request.clock.start('getACL')
         # Try the cache or parse acl and update the cache
         currentRevision = self.current_rev()
@@ -1628,16 +1627,16 @@
         The effective ACL is always from the last revision, even if
         you access an older revision.
         """
-        import wikiacl
+        from MoinMoin import security
         if self.exists() and self.rev == 0:
-            return wikiacl.parseACL(self.request, self.get_raw_body())
+            return security.parseACL(self.request, self.get_raw_body())
         try:
             lastRevision = self.getRevList()[0]
         except IndexError:
-            return wikiacl.AccessControlList(self.request)
+            return security.AccessControlList(self.request)
         body = Page(self.request, self.page_name,
                     rev=lastRevision).get_raw_body()
-        return wikiacl.parseACL(self.request, body)
+        return security.parseACL(self.request, body)
 
     def clean_acl_cache(self):
         """
--- a/MoinMoin/PageEditor.py	Tue Jun 06 21:39:16 2006 +0200
+++ b/MoinMoin/PageEditor.py	Tue Jun 06 22:52:53 2006 +0200
@@ -943,7 +943,7 @@
             msg = _('You did not change the page content, not saved!')
             raise self.Unchanged, msg
         else:
-            from wikiacl import parseACL
+            from MoinMoin.security import parseACL
             # Get current ACL and compare to new ACL from newtext. If
             # they are not the sames, the user must have admin
             # rights. This is a good place to update acl cache - instead
--- a/MoinMoin/_tests/test_wikiacl.py	Tue Jun 06 21:39:16 2006 +0200
+++ b/MoinMoin/_tests/test_wikiacl.py	Tue Jun 06 22:52:53 2006 +0200
@@ -1,6 +1,6 @@
 # -*- coding: iso-8859-1 -*-
 """
-    MoinMoin - MoinMoin.wikiacl Tests
+    MoinMoin - MoinMoin.security Tests
 
     @copyright: 2003-2004 by Jürgen Hermann <jh@web.de>
     @license: GNU GPL, see COPYING for details.
@@ -8,9 +8,9 @@
 
 import unittest
 from MoinMoin._tests import TestConfig
-from MoinMoin import config, wikiacl, _tests
+from MoinMoin import config, security, _tests
 
-acliter = wikiacl.ACLStringIterator
+acliter = security.ACLStringIterator
 
 class ACLStringIteratorTestCase(unittest.TestCase):
     
@@ -22,51 +22,46 @@
         del self.config
         
     def testEmpty(self):
-        """ wikiacl: empty acl string raise StopIteration """
+        """ security: empty acl string raise StopIteration """
         iter = acliter(self.request.cfg.acl_rights_valid, '')
         self.failUnlessRaises(StopIteration, iter.next)
 
     def testWhiteSpace(self):
-        """ wikiacl: white space acl string raise StopIteration """
+        """ security: white space acl string raise StopIteration """
         iter = acliter(self.request.cfg.acl_rights_valid, '       ')
         self.failUnlessRaises(StopIteration, iter.next)
             
     def testDefault(self):
-        """ wikiacl: default meta acl """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'Default Default')
+        """ security: default meta acl """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'Default Default')
         for mod, entries, rights in iter:
             self.assertEqual(entries, ['Default'])
             self.assertEqual(rights, [])
                 
     def testEmptyRights(self):
-        """ wikiacl: empty rights """    
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'WikiName:')
+        """ security: empty rights """    
+        iter = acliter(self.request.cfg.acl_rights_valid, 'WikiName:')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['WikiName'])
         self.assertEqual(rights, [])
 
     def testSingleWikiNameSingleWrite(self):
-        """ wikiacl: single wiki name, single right """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'WikiName:read')
+        """ security: single wiki name, single right """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'WikiName:read')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['WikiName'])
         self.assertEqual(rights, ['read'])
 
     def testMultipleWikiNameAndRights(self):
-        """ wikiacl: multiple wiki names and rights """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne,UserTwo:read,write')
+        """ security: multiple wiki names and rights """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne,UserTwo:read,write')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['UserOne', 'UserTwo'])
         self.assertEqual(rights, ['read', 'write'])      
         
     def testMultipleEntries(self):
-        """ wikiacl: multiple entries """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne:read,write UserTwo:read All:')
+        """ security: multiple entries """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read,write UserTwo:read All:')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['UserOne'])
         self.assertEqual(rights, ['read', 'write'])      
@@ -78,25 +73,22 @@
         self.assertEqual(rights, [])      
        
     def testNameWithSpaces(self):
-        """ wikiacl: single name with spaces """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'user one:read')
+        """ security: single name with spaces """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'user one:read')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['user one'])
         self.assertEqual(rights, ['read'])
 
     def testMultipleWikiNameAndRights(self):
-        """ wikiacl: multiple names with spaces """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'user one,user two:read')
+        """ security: multiple names with spaces """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'user one,user two:read')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['user one', 'user two'])
         self.assertEqual(rights, ['read'])      
         
     def testMultipleEntriesWithSpaces(self):
-        """ wikiacl: multiple entries with spaces """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'user one:read,write user two:read')
+        """ security: multiple entries with spaces """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'user one:read,write user two:read')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['user one'])
         self.assertEqual(rights, ['read', 'write'])      
@@ -105,9 +97,8 @@
         self.assertEqual(rights, ['read'])      
          
     def testMixedNames(self):
-        """ wikiacl: mixed wiki names and names with spaces """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne,user two:read,write user three,UserFour:read')
+        """ security: mixed wiki names and names with spaces """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne,user two:read,write user three,UserFour:read')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['UserOne', 'user two'])
         self.assertEqual(rights, ['read', 'write'])      
@@ -116,9 +107,8 @@
         self.assertEqual(rights, ['read'])      
 
     def testModifier(self):
-        """ wikiacl: acl modifiers """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       '+UserOne:read -UserTwo:')
+        """ security: acl modifiers """
+        iter = acliter(self.request.cfg.acl_rights_valid, '+UserOne:read -UserTwo:')
         mod, entries, rights = iter.next()
         self.assertEqual(mod, '+')
         self.assertEqual(entries, ['UserOne'])
@@ -129,26 +119,24 @@
         self.assertEqual(rights, [])
         
     def testIgnoreInvalidACL(self):
-        """ wikiacl: ignore invalid acl
+        """ security: ignore invalid acl
 
         The last part of this acl can not be parsed. If it ends with :
         then it will be parsed as one name with spaces.
         """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne:read user two is ignored')
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read user two is ignored')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['UserOne'])
         self.assertEqual(rights, ['read'])
         self.failUnlessRaises(StopIteration, iter.next)
         
     def testEmptyNamesWithRight(self):
-        """ wikiacl: empty names with rights
+        """ security: empty names with rights
 
         The documents does not talk about this case, may() should ignore
         the rights because there is no entry.
         """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne:read :read All:')
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read :read All:')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['UserOne'])
         self.assertEqual(rights, ['read'])
@@ -160,28 +148,25 @@
         self.assertEqual(rights, [])
 
     def testIgnodeInvalidRights(self):
-        """ wikiacl: ignore rights not in acl_rights_valid """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne:read,sing,write,drink,sleep')
+        """ security: ignore rights not in acl_rights_valid """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read,sing,write,drink,sleep')
         mod, entries, rights = iter.next()
         self.assertEqual(rights, ['read', 'write'])        
 
     def testBadGuy(self):
-        """ wikiacl: bad guy may not allowed anything
+        """ security: bad guy may not allowed anything
 
         This test was failing on the apply acl rights test.
         """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne:read,write BadGuy: All:read')
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne:read,write BadGuy: All:read')
         mod, entries, rights = iter.next()
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['BadGuy'])
         self.assertEqual(rights, [])
 
     def testAllowExtraWhitespace(self):
-        """ wikiacl: allow extra white space between entries """
-        iter = acliter(self.request.cfg.acl_rights_valid,
-                       'UserOne,user two:read,write   user three,UserFour:read  All:')
+        """ security: allow extra white space between entries """
+        iter = acliter(self.request.cfg.acl_rights_valid, 'UserOne,user two:read,write   user three,UserFour:read  All:')
         mod, entries, rights = iter.next()
         self.assertEqual(entries, ['UserOne', 'user two'])
         self.assertEqual(rights, ['read', 'write'])      
@@ -194,14 +179,13 @@
        
 
 class AclTestCase(unittest.TestCase):
-    """wikiacl: testing access control list
+    """ security: testing access control list
 
     TO DO: test unknown user?
     """
     def setUp(self):
         # Backup user
-        self.config = TestConfig(self.request,
-                                 defaults=['acl_rights_valid', 'acl_rights_before'])
+        self.config = TestConfig(self.request, defaults=['acl_rights_valid', 'acl_rights_before'])
         self.savedUser = self.request.user.name
         
     def tearDown(self):
@@ -210,7 +194,7 @@
         del self.config
         
     def testApplyACLByUser(self):
-        """wikiacl: applying acl by user name"""
+        """ security: applying acl by user name"""
         # This acl string...
         acl_rights = [
             "Admin1,Admin2:read,write,delete,revert,admin  "
@@ -221,7 +205,7 @@
             "BadGuy:  "
             "All:read  "
             ]
-        acl = wikiacl.AccessControlList(self.request, acl_rights)
+        acl = security.AccessControlList(self.request, acl_rights)
 
         # Should apply these rights:
         users = (
--- a/MoinMoin/i18n/POTFILES.in	Tue Jun 06 21:39:16 2006 +0200
+++ b/MoinMoin/i18n/POTFILES.in	Tue Jun 06 22:52:53 2006 +0200
@@ -9,7 +9,6 @@
 user.py
 userform.py
 version.py
-wikiacl.py
 wikiutil.py
 wikidicts.py
 failure.py
--- a/MoinMoin/security/__init__.py	Tue Jun 06 21:39:16 2006 +0200
+++ b/MoinMoin/security/__init__.py	Tue Jun 06 22:52:53 2006 +0200
@@ -14,6 +14,9 @@
     @license: GNU GPL, see COPYING for details.
 """
 
+import re
+from MoinMoin import user
+
 #############################################################################
 ### Basic Permissions Interface -- most features enabled by default
 #############################################################################
@@ -57,3 +60,348 @@
 # make an alias for the default policy
 Default = Permissions
 
+
+# moved from MoinMoin.wikiacl ------------------------------------------------
+"""
+    MoinMoin Access Control Lists
+
+    @copyright: 2003 by Thomas Waldmann, http://linuxwiki.de/ThomasWaldmann
+    @copyright: 2003 by Gustavo Niemeyer, http://moin.conectiva.com.br/GustavoNiemeyer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+class AccessControlList:
+    ''' Access Control List
+
+    Control who may do what on or with a wiki page.
+
+    Syntax of an ACL string:
+    
+        [+|-]User[,User,...]:[right[,right,...]] [[+|-]SomeGroup:...] ...
+        ... [[+|-]Known:...] [[+|-]All:...]
+
+        "User" is a user name and triggers only if the user matches. Up
+        to version 1.2 only WikiNames were supported, as of version 1.3,
+        any name can be used in acl lines, including name with spaces
+        using esoteric languages.
+
+        "SomeGroup" is a page name matching cfg.page_group_regex with
+         some lines in the form " * Member", defining the group members.
+
+        "Known" is a group containing all valid / known users.
+
+        "All" is a group containing all users (Known and Anonymous users).
+
+        "right" may be an arbitrary word like read, write, delete, admin.
+        Only words in cfg.acl_validrights are accepted, others are
+        ignored. It is allowed to specify no rights, which means that no
+        rights are given.
+
+    How ACL is processed
+
+        When some user is trying to access some ACL-protected resource,
+        the ACLs will be processed in the order they are found. The first
+        matching ACL will tell if the user has access to that resource
+        or not.
+
+        For example, the following ACL tells that SomeUser is able to
+        read and write the resources protected by that ACL, while any
+        member of SomeGroup (besides SomeUser, if part of that group)
+        may also admin that, and every other user is able to read it.
+
+            SomeUser:read,write SomeGroup:read,write,admin All:read
+
+        In this example, SomeUser can read and write but can not admin,
+        revert or delete pages. Rights that are NOT specified on the
+        right list are automatically set to NO.
+
+    Using Prefixes
+        
+        To make the system more flexible, there are also two modifiers:
+        the prefixes "+" and "-". 
+
+            +SomeUser:read -OtherUser:write
+
+        The acl line above will grant SomeUser read right, and OtherUser
+        write right, but will NOT block automatically all other rights
+        for these users. For example, if SomeUser ask to write, the
+        above acl line does not define if he can or can not write. He
+        will be able to write if acl_rights_before or acl_rights_after
+        allow this (see configuration options).
+        
+        Using prefixes, this acl line:
+        
+            SomeUser:read,write SomeGroup:read,write,admin All:read
+
+        Can be written as:
+        
+            -SomeUser:admin SomeGroup:read,write,admin All:read
+
+        Or even:
+
+            +All:read -SomeUser:admin SomeGroup:read,write,admin
+
+        Notice that you probably will not want to use the second and
+        third examples in ACL entries of some page. They are very
+        useful on the moin configuration entries though.
+
+   Configuration options
+   
+       cfg.acl_rights_default
+           It is is ONLY used when no other ACLs are given.
+           Default: "Known:read,write,delete All:read,write",
+
+       cfg.acl_rights_before
+           When the page has ACL entries, this will be inserted BEFORE
+           any page entries.
+           Default: ""
+
+       cfg.acl_rights_after
+           When the page has ACL entries, this will be inserted AFTER
+           any page entries.
+           Default: ""
+       
+       cfg.acl_rights_valid
+           These are the acceptable (known) rights (and the place to
+           extend, if necessary).
+           Default: ["read", "write", "delete", "admin"]
+    '''
+
+    special_users = ["All", "Known", "Trusted"] # order is important
+
+    def __init__(self, request, lines=[]):
+        """Initialize an ACL, starting from <nothing>.
+        """
+        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)
+
+    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
+
+        This can be used in multiple subsequent calls to process longer
+        lists.
+
+        @param cfg: current config
+        @param aclstring: acl string from page or cfg
+        @param remember: should add the line to self.acl_lines
+        """
+        group_re = re.compile(cfg.page_group_regex)
+
+        # Remember lines
+        if remember:
+            self.acl_lines.append(aclstring)
+
+        # Iterate over entries and rights, parsed by acl string iterator
+        acliter = ACLStringIterator(cfg.acl_rights_valid, aclstring)
+        for modifier, entries, rights in acliter:
+            if entries == ['Default']:
+                self.addDefault(cfg)
+                continue
+            
+            for entry in entries:
+                if group_re.search(entry):
+                    self._is_group[entry] = 1
+                rightsdict = {}
+                if modifier:
+                    # Only user rights are added to the right dict.
+                    # + add rights with value of 1
+                    # - add right with value of 0
+                    for right in rights:
+                        rightsdict[right] = (modifier == '+')    
+                else:
+                    # All rights from acl_rights_valid are added to the
+                    # dict, user rights with value of 1, and other with
+                    # value of 0
+                    for right in cfg.acl_rights_valid:
+                        rightsdict[right] = (right in rights)
+                self.acl.append((entry, rightsdict))
+
+    def may(self, request, name, dowhat):
+        """May <name> <dowhat>?
+           Returns boolean answer.
+        """
+        is_group_member = request.dicts.has_member
+
+        allowed = None
+        for entry, rightsdict in self.acl:
+            if entry in self.special_users:
+                handler = getattr(self, "_special_"+entry, None)
+                allowed = handler(request, name, dowhat, rightsdict)
+            elif self._is_group.get(entry):
+                if is_group_member(entry, name):
+                    allowed = rightsdict.get(dowhat)
+                else:
+                    for special in self.special_users:
+                        if is_group_member(entry, special):
+                            handler = getattr(self, "_special_"+ special, None)
+                            allowed = handler(request, name,
+                                              dowhat, rightsdict)
+                            break # order of self.special_users is important
+            elif entry == name:
+                allowed = rightsdict.get(dowhat)
+            if allowed is not None:
+                return allowed
+        return 0
+
+    def getString(self, b='#acl ', e='\n'):
+        """print the acl strings we were fed with"""
+        return ''.join(["%s%s%s" % (b,l,e) for l in self.acl_lines])
+
+    def _special_All(self, request, name, dowhat, rightsdict):
+        return rightsdict.get(dowhat)
+
+    def _special_Known(self, request, name, dowhat, rightsdict):
+        """ check if user <name> is known to us,
+            that means that there is a valid user account present.
+            works for subscription emails.
+        """
+        if user.getUserId(request, name): # is a user with this name known?
+            return rightsdict.get(dowhat)
+        return None
+
+    def _special_Trusted(self, request, name, dowhat, rightsdict):
+        """ check if user <name> is known AND even has logged in using a password.
+            does not work for subsription emails that should be sent to <user>,
+            as he is not logged in in that case.
+        """
+        if request.user.trusted and name == request.user.name:
+            return rightsdict.get(dowhat)
+        return None
+
+    def __eq__(self, other):
+        return self.acl_lines == other.acl_lines
+    def __ne__(self, other):
+        return self.acl_lines != other.acl_lines
+
+
+class ACLStringIterator:
+    """ Iterator for acl string
+
+    Parse acl string and return the next entry on each call to
+    next. Implement the Iterator protocol.
+
+    Usage:
+        iter = ACLStringIterator(cfg.acl_rights_valid, 'user name:right')
+        for modifier, entries, rights in iter:
+            # process data
+    """
+    
+    def __init__(self, rights, aclstring):
+        """ Initialize acl iterator
+
+        @param rights: the acl rights to consider when parsing
+        @param aclstring: string to parse
+        """
+        self.rights = rights
+        self.rest = aclstring.strip()
+        self.finished = 0
+
+    def __iter__(self):
+        """ Required by the Iterator protocol """
+        return self
+    
+    def next(self):
+        """ Return the next values from the acl string
+
+        When the iterator is finished and you try to call next, it
+        raises a StopIteration. The iterator finish as soon as the
+        string is fully parsed or can not be parsed any more.
+
+        @rtype: 3 tuple - (modifier, [entry, ...], [right, ...])
+        @return: values for one item in an acl string
+        """
+        # Handle finished state, required by iterator protocol
+        if self.rest == '':
+            self.finished = 1
+        if self.finished:
+            raise StopIteration
+        
+        # Get optional modifier [+|-]entries:rights
+        modifier = ''
+        if self.rest[0] in ('+', '-'):
+            modifier, self.rest = self.rest[0], self.rest[1:]
+
+        # Handle the Default meta acl
+        if self.rest.startswith('Default ') or self.rest == 'Default':
+            self.rest = self.rest[8:]           
+            entries, rights = ['Default'], []
+            
+        # Handle entries:rights pairs
+        else:
+            # Get entries
+            try:
+                entries, self.rest = self.rest.split(':', 1)
+            except ValueError:
+                self.finished = 1
+                raise StopIteration("Can't parse rest of string")
+            if entries == '':
+                entries = []
+            else:
+                # TODO strip each entry from blanks?
+                entries = entries.split(',')            
+
+            # Get rights
+            try:         
+                rights, self.rest = self.rest.split(' ', 1)
+                # Remove extra white space after rights fragment,
+                # allowing using multiple spaces between items.
+                self.rest = self.rest.lstrip()
+            except ValueError:
+                rights, self.rest = self.rest, ''
+            rights = [r for r in rights.split(',') if r in self.rights]
+
+        return modifier, entries, rights
+
+
+def parseACL(request, body):
+    """ Parse acl lines on page and return ACL object
+
+    Use ACL object may(request, dowhat) to get acl rights.
+    """
+    acl_lines = []
+    while body and body[0] == '#':
+        # extract first line
+        try:
+            line, body = body.split('\n', 1)
+        except ValueError:
+            line = body
+            body = ''
+
+        # end parsing on empty (invalid) PI
+        if line == "#":
+            break
+
+        # skip comments (lines with two hash marks)
+        if line[1] == '#':
+            continue
+
+        tokens = line.split(None, 1)
+        if tokens[0].lower() == "#acl":
+            if len(tokens) == 2:
+                args = tokens[1].rstrip()
+            else:
+                args = ""
+            acl_lines.append(args)
+    return AccessControlList(request, acl_lines)
+
--- a/MoinMoin/wikiacl.py	Tue Jun 06 21:39:16 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,347 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin Access Control Lists
-
-    @copyright: 2003 by Thomas Waldmann, http://linuxwiki.de/ThomasWaldmann
-    @copyright: 2003 by Gustavo Niemeyer, http://moin.conectiva.com.br/GustavoNiemeyer
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import re
-from MoinMoin import user
-
-class AccessControlList:
-    ''' Access Control List
-
-    Control who may do what on or with a wiki page.
-
-    Syntax of an ACL string:
-    
-        [+|-]User[,User,...]:[right[,right,...]] [[+|-]SomeGroup:...] ...
-        ... [[+|-]Known:...] [[+|-]All:...]
-
-        "User" is a user name and triggers only if the user matches. Up
-        to version 1.2 only WikiNames were supported, as of version 1.3,
-        any name can be used in acl lines, including name with spaces
-        using esoteric languages.
-
-        "SomeGroup" is a page name matching cfg.page_group_regex with
-         some lines in the form " * Member", defining the group members.
-
-        "Known" is a group containing all valid / known users.
-
-        "All" is a group containing all users (Known and Anonymous users).
-
-        "right" may be an arbitrary word like read, write, delete, admin.
-        Only words in cfg.acl_validrights are accepted, others are
-        ignored. It is allowed to specify no rights, which means that no
-        rights are given.
-
-    How ACL is processed
-
-        When some user is trying to access some ACL-protected resource,
-        the ACLs will be processed in the order they are found. The first
-        matching ACL will tell if the user has access to that resource
-        or not.
-
-        For example, the following ACL tells that SomeUser is able to
-        read and write the resources protected by that ACL, while any
-        member of SomeGroup (besides SomeUser, if part of that group)
-        may also admin that, and every other user is able to read it.
-
-            SomeUser:read,write SomeGroup:read,write,admin All:read
-
-        In this example, SomeUser can read and write but can not admin,
-        revert or delete pages. Rights that are NOT specified on the
-        right list are automatically set to NO.
-
-    Using Prefixes
-        
-        To make the system more flexible, there are also two modifiers:
-        the prefixes "+" and "-". 
-
-            +SomeUser:read -OtherUser:write
-
-        The acl line above will grant SomeUser read right, and OtherUser
-        write right, but will NOT block automatically all other rights
-        for these users. For example, if SomeUser ask to write, the
-        above acl line does not define if he can or can not write. He
-        will be able to write if acl_rights_before or acl_rights_after
-        allow this (see configuration options).
-        
-        Using prefixes, this acl line:
-        
-            SomeUser:read,write SomeGroup:read,write,admin All:read
-
-        Can be written as:
-        
-            -SomeUser:admin SomeGroup:read,write,admin All:read
-
-        Or even:
-
-            +All:read -SomeUser:admin SomeGroup:read,write,admin
-
-        Notice that you probably will not want to use the second and
-        third examples in ACL entries of some page. They are very
-        useful on the moin configuration entries though.
-
-   Configuration options
-   
-       cfg.acl_rights_default
-           It is is ONLY used when no other ACLs are given.
-           Default: "Known:read,write,delete All:read,write",
-
-       cfg.acl_rights_before
-           When the page has ACL entries, this will be inserted BEFORE
-           any page entries.
-           Default: ""
-
-       cfg.acl_rights_after
-           When the page has ACL entries, this will be inserted AFTER
-           any page entries.
-           Default: ""
-       
-       cfg.acl_rights_valid
-           These are the acceptable (known) rights (and the place to
-           extend, if necessary).
-           Default: ["read", "write", "delete", "admin"]
-    '''
-
-    special_users = ["All", "Known", "Trusted"] # order is important
-
-    def __init__(self, request, lines=[]):
-        """Initialize an ACL, starting from <nothing>.
-        """
-        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)
-
-    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
-
-        This can be used in multiple subsequent calls to process longer
-        lists.
-
-        @param cfg: current config
-        @param aclstring: acl string from page or cfg
-        @param remember: should add the line to self.acl_lines
-        """
-        group_re = re.compile(cfg.page_group_regex)
-
-        # Remember lines
-        if remember:
-            self.acl_lines.append(aclstring)
-
-        # Iterate over entries and rights, parsed by acl string iterator
-        acliter = ACLStringIterator(cfg.acl_rights_valid, aclstring)
-        for modifier, entries, rights in acliter:
-            if entries == ['Default']:
-                self.addDefault(cfg)
-                continue
-            
-            for entry in entries:
-                if group_re.search(entry):
-                    self._is_group[entry] = 1
-                rightsdict = {}
-                if modifier:
-                    # Only user rights are added to the right dict.
-                    # + add rights with value of 1
-                    # - add right with value of 0
-                    for right in rights:
-                        rightsdict[right] = (modifier == '+')    
-                else:
-                    # All rights from acl_rights_valid are added to the
-                    # dict, user rights with value of 1, and other with
-                    # value of 0
-                    for right in cfg.acl_rights_valid:
-                        rightsdict[right] = (right in rights)
-                self.acl.append((entry, rightsdict))
-
-    def may(self, request, name, dowhat):
-        """May <name> <dowhat>?
-           Returns boolean answer.
-        """
-        is_group_member = request.dicts.has_member
-
-        allowed = None
-        for entry, rightsdict in self.acl:
-            if entry in self.special_users:
-                handler = getattr(self, "_special_"+entry, None)
-                allowed = handler(request, name, dowhat, rightsdict)
-            elif self._is_group.get(entry):
-                if is_group_member(entry, name):
-                    allowed = rightsdict.get(dowhat)
-                else:
-                    for special in self.special_users:
-                        if is_group_member(entry, special):
-                            handler = getattr(self, "_special_"+ special, None)
-                            allowed = handler(request, name,
-                                              dowhat, rightsdict)
-                            break # order of self.special_users is important
-            elif entry == name:
-                allowed = rightsdict.get(dowhat)
-            if allowed is not None:
-                return allowed
-        return 0
-
-    def getString(self, b='#acl ', e='\n'):
-        """print the acl strings we were fed with"""
-        return ''.join(["%s%s%s" % (b,l,e) for l in self.acl_lines])
-
-    def _special_All(self, request, name, dowhat, rightsdict):
-        return rightsdict.get(dowhat)
-
-    def _special_Known(self, request, name, dowhat, rightsdict):
-        """ check if user <name> is known to us,
-            that means that there is a valid user account present.
-            works for subscription emails.
-        """
-        if user.getUserId(request, name): # is a user with this name known?
-            return rightsdict.get(dowhat)
-        return None
-
-    def _special_Trusted(self, request, name, dowhat, rightsdict):
-        """ check if user <name> is known AND even has logged in using a password.
-            does not work for subsription emails that should be sent to <user>,
-            as he is not logged in in that case.
-        """
-        if request.user.trusted and name == request.user.name:
-            return rightsdict.get(dowhat)
-        return None
-
-    def __eq__(self, other):
-        return self.acl_lines == other.acl_lines
-    def __ne__(self, other):
-        return self.acl_lines != other.acl_lines
-
-
-class ACLStringIterator:
-    """ Iterator for acl string
-
-    Parse acl string and return the next entry on each call to
-    next. Implement the Iterator protocol.
-
-    Usage:
-        iter = ACLStringIterator(cfg.acl_rights_valid, 'user name:right')
-        for modifier, entries, rights in iter:
-            # process data
-    """
-    
-    def __init__(self, rights, aclstring):
-        """ Initialize acl iterator
-
-        @param rights: the acl rights to consider when parsing
-        @param aclstring: string to parse
-        """
-        self.rights = rights
-        self.rest = aclstring.strip()
-        self.finished = 0
-
-    def __iter__(self):
-        """ Required by the Iterator protocol """
-        return self
-    
-    def next(self):
-        """ Return the next values from the acl string
-
-        When the iterator is finished and you try to call next, it
-        raises a StopIteration. The iterator finish as soon as the
-        string is fully parsed or can not be parsed any more.
-
-        @rtype: 3 tuple - (modifier, [entry, ...], [right, ...])
-        @return: values for one item in an acl string
-        """
-        # Handle finished state, required by iterator protocol
-        if self.rest == '':
-            self.finished = 1
-        if self.finished:
-            raise StopIteration
-        
-        # Get optional modifier [+|-]entries:rights
-        modifier = ''
-        if self.rest[0] in ('+', '-'):
-            modifier, self.rest = self.rest[0], self.rest[1:]
-
-        # Handle the Default meta acl
-        if self.rest.startswith('Default ') or self.rest == 'Default':
-            self.rest = self.rest[8:]           
-            entries, rights = ['Default'], []
-            
-        # Handle entries:rights pairs
-        else:
-            # Get entries
-            try:
-                entries, self.rest = self.rest.split(':', 1)
-            except ValueError:
-                self.finished = 1
-                raise StopIteration("Can't parse rest of string")
-            if entries == '':
-                entries = []
-            else:
-                # TODO strip each entry from blanks?
-                entries = entries.split(',')            
-
-            # Get rights
-            try:         
-                rights, self.rest = self.rest.split(' ', 1)
-                # Remove extra white space after rights fragment,
-                # allowing using multiple spaces between items.
-                self.rest = self.rest.lstrip()
-            except ValueError:
-                rights, self.rest = self.rest, ''
-            rights = [r for r in rights.split(',') if r in self.rights]
-
-        return modifier, entries, rights
-
-
-def parseACL(request, body):
-    """ Parse acl lines on page and return ACL object
-
-    Use ACL object may(request, dowhat) to get acl rights.
-    """
-    acl_lines = []
-    while body and body[0] == '#':
-        # extract first line
-        try:
-            line, body = body.split('\n', 1)
-        except ValueError:
-            line = body
-            body = ''
-
-        # end parsing on empty (invalid) PI
-        if line == "#":
-            break
-
-        # skip comments (lines with two hash marks)
-        if line[1] == '#':
-            continue
-
-        tokens = line.split(None, 1)
-        if tokens[0].lower() == "#acl":
-            if len(tokens) == 2:
-                args = tokens[1].rstrip()
-            else:
-                args = ""
-            acl_lines.append(args)
-    return AccessControlList(request, acl_lines)
-
--- a/docs/CHANGES	Tue Jun 06 21:39:16 2006 +0200
+++ b/docs/CHANGES	Tue Jun 06 22:52:53 2006 +0200
@@ -58,7 +58,8 @@
       TWISTED not
     * moved util/antispam.py to security/antispam.py,
       moved util/autoadmin.py to security/autoadmin.py,
-      moved security.py to security/__init__.py
+      moved security.py to security/__init__.py,
+      moved wikiacl.py to security/__init__.py.
     * added wikiutil.MimeType class (works internally with sanitized mime
       types because the official ones suck)
     * renamed parsers to module names representing sane mimetypes, e.g.: