changeset 4787:de7ca86a9f62

Groups2009: MoinMoin.groups.backends.wiki_group backend was added. Cache management methods load_cache and update_cache for the GroupManager. request.groups proviedes access to group definitions. request.dits provides acces to the http://moinmo.in/WikiDict
author Dmitrijs Milajevs <dimazest@gmail.com>
date Sat, 06 Jun 2009 11:48:40 +0200
parents f0c2ab8ef256
children 1ff6498db9ec
files MoinMoin/events/wikidictsrescan.py MoinMoin/groups/__init__.py MoinMoin/groups/backends/__init__.py MoinMoin/groups/backends/_tests/test_wiki_group.py MoinMoin/groups/backends/wiki_group.py MoinMoin/web/contexts.py docs/CHANGES.dmilajevs
diffstat 6 files changed, 485 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/events/wikidictsrescan.py	Sat Jun 06 11:48:29 2009 +0200
+++ b/MoinMoin/events/wikidictsrescan.py	Sat Jun 06 11:48:40 2009 +0200
@@ -12,6 +12,7 @@
 logging = log.getLogger(__name__)
 
 from MoinMoin import events as ev
+from MoinMoin.groups.backends import wiki_group
 from MoinMoin import wikidicts
 
 def handle(event):
@@ -39,3 +40,7 @@
     gd.scan_dicts()
     logging.debug("groupsdicts changed: scan_dicts finished")
 
+    logging.debug("groups changed: %r, update_cache started", page.page_name)
+    request.groups.update_cache()
+    logging.debug("groups changed: update_cache finished")
+
--- a/MoinMoin/groups/__init__.py	Sat Jun 06 11:48:29 2009 +0200
+++ b/MoinMoin/groups/__init__.py	Sat Jun 06 11:48:40 2009 +0200
@@ -98,7 +98,7 @@
         for backend in self._backends:
             if group_name in backend:
                 return backend[group_name]
-        raise KeyError("There is no such group")
+        raise KeyError("There is no such group %s" % group_name)
 
     def __iter__(self):
         """
@@ -128,3 +128,16 @@
         """
         return [group_name for group_name in self
                          if member in self[group_name]]
+
+    def update_cache(self):
+        for backend in self._backends:
+            update_cache = getattr(backend, 'update_cache', None)
+            if callable(update_cache):
+                update_cache()
+
+
+    def load_cache(self):
+        for backend in self._backends:
+            load_cache = getattr(backend, 'load_cache', None)
+            if callable(load_cache):
+                load_cache()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/groups/backends/_tests/test_wiki_group.py	Sat Jun 06 11:48:40 2009 +0200
@@ -0,0 +1,352 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - MoinMoin.groups.backends.wiki_group tests
+
+    @copyright: 2003-2004 by Juergen Hermann <jh@web.de>,
+                2007 by MoinMoin:ThomasWaldmann
+                2008 by MoinMoin:MelitaMihaljevic
+                2009 by MoinMoin:DmitrijsMilajevs
+    @license: GNU GPL, see COPYING for details.
+
+"""
+
+import py
+import re
+import shutil
+
+from py.test import raises
+
+from MoinMoin.groups.backends import wiki_group
+from MoinMoin import Page, security
+from MoinMoin.PageEditor import PageEditor
+from MoinMoin.user import User
+from MoinMoin._tests import append_page, become_trusted, create_page, create_random_string_list, nuke_page, nuke_user, wikiconfig
+from MoinMoin.groups import GroupManager
+
+class TestWikiGroupPage:
+    """
+    Test what backend extracts from a group page and what is ignored.
+    """
+
+    class Config(wikiconfig.Config):
+        def group_manager_init(self, request):
+            return GroupManager(backends=[wiki_group.Backend(request)])
+
+    def test_CamelCase(self):
+        text = """
+ * CamelCase
+"""
+        assert u'CamelCase' in self.get_group(text)
+
+    def test_extended_name(self):
+        text = """
+ * extended name
+"""
+        assert u'extended name' in self.get_group(text)
+
+    def test_extended_link(self):
+        text = """
+ * [[extended link]]
+"""
+        assert u'extended link' in self.get_group(text)
+
+    def test_ignore_second_level_list(self):
+        text = """
+  * second level
+   * third level
+    * forth level
+     * and then some...
+"""
+        assert len([x for x in self.get_group(text)]) == 0
+
+    def test_ignore_other(self):
+        text = """
+= ignore this =
+ * take this
+
+Ignore previous line and this text.
+"""
+        assert u'take this' in self.get_group(text)
+
+    def test_strip_whitespace(self):
+        text = """
+ *   take this
+"""
+        assert u'take this' in self.get_group(text)
+
+    def get_group(self, text):
+        request = self.request
+        become_trusted(request)
+        create_page(request, u'SomeTestGroup', text)
+        group = request.groups[u'SomeTestGroup']
+        nuke_page(request, u'SomeTestGroup')
+        return group
+
+
+class TestWikiGroupBackend:
+
+    class Config(wikiconfig.Config):
+        def group_manager_init(self, request):
+            return GroupManager(backends=[wiki_group.Backend(request)])
+
+    def setup_method(self, method):
+
+        become_trusted(self.request)
+
+        self.wiki_group_page_name = u'TestWikiGroup'
+        wiki_group_page_text = u"""
+ * Apple
+ * Banana
+ * OtherGroup"""
+        create_page(self.request, self.wiki_group_page_name, wiki_group_page_text)
+
+        self.other_group_page_name = u'OtherGroup'
+        other_group_page_text = u"""
+ * Admin
+ * Editor
+ * Apple"""
+        create_page(self.request, self.other_group_page_name, other_group_page_text)
+
+        self.third_group_page_name = u'ThirdGroup'
+        third_group_page_text = u' * Other'
+        create_page(self.request, self.third_group_page_name, third_group_page_text)
+
+    def teardown_method(self, method):
+        become_trusted(self.request)
+        nuke_page(self.request, self.wiki_group_page_name)
+        nuke_page(self.request, self.other_group_page_name)
+        nuke_page(self.request, self.third_group_page_name)
+
+    def test_contains(self):
+        """
+        Test group_wiki Backend and Group containment methods.
+        """
+        groups = self.request.groups
+
+        assert u'TestWikiGroup' in groups
+        assert u'Banana' in groups[u'TestWikiGroup']
+        assert u'Apple' in groups[u'TestWikiGroup']
+        assert u'Admin' in groups[u'TestWikiGroup'], 'Groups must be automatically expanded'
+
+        assert u'OtherGroup' in groups
+        assert u'Apple' in groups[u'OtherGroup']
+        assert u'Admin' in groups[u'OtherGroup']
+
+        assert u'NotExistingGroup' not in groups
+        raises(KeyError, lambda: groups[u'NotExistingGroup'])
+
+        assert u'ThirdGroup' in groups
+
+    def test_membergroups(self):
+        groups = self.request.groups
+
+        apple_groups = groups.membergroups(u'Apple')
+        assert 2 == len(apple_groups)
+        assert u'TestWikiGroup' in apple_groups
+        assert u'OtherGroup' in apple_groups
+        assert u'ThirdGroup' not in apple_groups
+
+    def test_rename_group_page(self):
+        """
+        Tests if the groups cache is refreshed after renaming a Group page.
+        """
+        request = self.request
+        become_trusted(request)
+
+        page = create_page(request, u'SomeGroup', u" * ExampleUser")
+        page.renamePage('AnotherGroup')
+
+        result = u'ExampleUser' in request.groups[u'AnotherGroup']
+        nuke_page(request, u'AnotherGroup')
+
+        assert result is True
+
+    def test_copy_group_page(self):
+        """
+        Tests if the groups cache is refreshed after copying a Group page.
+        """
+        request = self.request
+        become_trusted(request)
+
+        page = create_page(request, u'SomeGroup', u" * ExampleUser")
+        page.copyPage(u'SomeOtherGroup')
+
+        result = u'ExampleUser' in request.groups[u'SomeOtherGroup']
+
+        nuke_page(request, u'OtherGroup')
+        nuke_page(request, u'SomeGroup')
+
+        assert result is True
+
+    def test_appending_group_page(self):
+        """
+        Test scalability by appending a name to a large list of group members.
+        """
+        request = self.request
+        become_trusted(request)
+
+        # long list of users
+        page_content = [u" * %s" % member for member in create_random_string_list(length=15, count=30000)]
+        test_user = create_random_string_list(length=15, count=1)[0]
+        create_page(request, u'UserGroup', "\n".join(page_content))
+        append_page(request, u'UserGroup', u' * %s' % test_user)
+        result = test_user in request.groups['UserGroup']
+        nuke_page(request, u'UserGroup')
+
+        assert result
+
+    def test_user_addition_to_group_page(self):
+        """
+        Test addition of a username to a large list of group members.
+        """
+        request = self.request
+        become_trusted(request)
+
+        # long list of users
+        page_content = [u" * %s" % member for member in create_random_string_list()]
+        create_page(request, u'UserGroup', "\n".join(page_content))
+
+        new_user = create_random_string_list(length=15, count=1)[0]
+        append_page(request, u'UserGroup', u' * %s' % new_user)
+        user = User(request, name=new_user)
+        if not user.exists():
+            User(request, name=new_user, password=new_user).save()
+
+        result = new_user in request.groups[u'UserGroup']
+        nuke_page(request, u'UserGroup')
+        nuke_user(request, new_user)
+
+        assert result
+
+    def test_member_removed_from_group_page(self):
+        """
+        Tests appending a member to a large list of group members and
+        recreating the page without the member.
+        """
+        request = self.request
+        become_trusted(request)
+
+        # long list of users
+        page_content = [u" * %s" % member for member in create_random_string_list()]
+        page_content = "\n".join(page_content)
+        create_page(request, u'UserGroup', page_content)
+
+        test_user = create_random_string_list(length=15, count=1)[0]
+        page = append_page(request, u'UserGroup', u' * %s' % test_user)
+
+        # saves the text without test_user
+        page.saveText(page_content, 0)
+        result = test_user in request.groups[u'UserGroup']
+        nuke_page(request, u'UserGroup')
+
+        assert not result
+
+    def test_group_page_user_addition_trivial_change(self):
+        """
+        Test addition of a user to a group page by trivial change.
+        """
+        request = self.request
+        become_trusted(request)
+
+        test_user = create_random_string_list(length=15, count=1)[0]
+        member = u" * %s\n" % test_user
+        page = create_page(request, u'UserGroup', member)
+
+        # next member saved  as trivial change
+        test_user = create_random_string_list(length=15, count=1)[0]
+        member = u" * %s\n" % test_user
+        page.saveText(member, 0, trivial=1)
+
+        result = test_user in request.groups[u'UserGroup']
+
+        nuke_page(request, u'UserGroup')
+
+        assert result
+
+    def test_wiki_backend_acl_allow(self):
+        """
+        Test if the wiki group backend works with acl code.
+        Check user which has rights.
+        """
+        request = self.request
+        become_trusted(request)
+
+        create_page(request, u'FirstGroup', u" * OtherUser")
+
+        acl_rights = ["FirstGroup:admin,read,write"]
+        acl = security.AccessControlList(request.cfg, acl_rights)
+
+        allow = acl.may(request, u"OtherUser", "admin")
+
+        nuke_page(request, u'FirstGroup')
+
+        assert allow, 'OtherUser has read rights because he is member of FirstGroup'
+
+    def test_wiki_backend_acl_deny(self):
+        """
+        Test if the wiki group backend works with acl code.
+        Check user which does not have rights.
+        """
+        request = self.request
+        become_trusted(request)
+
+        create_page(request, u'FirstGroup', u" * OtherUser")
+
+        acl_rights = ["FirstGroup:read,write"]
+        acl = security.AccessControlList(request.cfg, acl_rights)
+
+        other_user_allow = acl.may(request, u"OtherUser", "admin")
+        some_user_allow = acl.may(request, u"SomeUser", "read")
+
+        nuke_page(request, u'FirstGroup')
+
+        assert not other_user_allow, 'OtherUser does not have admin rights because it is not listed in acl'
+        assert not some_user_allow, 'SomeUser does not have admin read right because he is not listed in the FirstGroup'
+
+    def test_wiki_backend_page_acl_append_page(self):
+        """
+        Test if the wiki group backend works with acl code.
+        First check acl rights of a user that is not a member of group
+        then add user member to a page group and check acl rights
+        """
+        request = self.request
+        become_trusted(request)
+
+        create_page(request, u'NewGroup', u" * ExampleUser")
+
+        acl_rights = ["NewGroup:read,write"]
+        acl = security.AccessControlList(request.cfg, acl_rights)
+
+        has_rights_before = acl.may(request, u"AnotherUser", "read")
+
+        # update page - add AnotherUser to a page group NewGroup
+        append_page(request, u'NewGroup', u" * AnotherUser")
+
+        has_rights_after = acl.may(request, u"AnotherUser", "read")
+
+        nuke_page(request, u'NewGroup')
+
+        assert not has_rights_before, 'AnotherUser has no read rights because in the beginning he is not a member of a group page NewGroup'
+        assert has_rights_after, 'AnotherUser must have read rights because after appendage he is member of NewGroup'
+
+
+    def test_wiki_backend_page_acl_with_all(self):
+        request = self.request
+        become_trusted(request)
+
+        acl_rights = ["OtherGroup:read,write,delete,admin All:read"]
+        acl = security.AccessControlList(request.cfg, acl_rights)
+
+        assert acl.may(request, u"Editor", "read")
+        assert acl.may(request, u"Editor", "write")
+        assert acl.may(request, u"Editor", "delete")
+        assert acl.may(request, u"Editor", "admin")
+
+        assert acl.may(request, u"Banana", "read")
+        assert not acl.may(request, u"Banana", "write")
+        assert not acl.may(request, u"Banana", "delete")
+        assert not acl.may(request, u"Banana", "admin")
+
+
+coverage_modules = ['MoinMoin.groups.backends.wiki_group']
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/groups/backends/wiki_group.py	Sat Jun 06 11:48:40 2009 +0200
@@ -0,0 +1,105 @@
+# -*- coding: iso-8859-1 -*-
+"""
+MoinMoin - wiki group backend
+
+The wiki group backend enables you to define groups on wiki pages.  To
+find group pages, request.cfg.cache.page_group_regexact pattern is
+used.  To find group members, it parses theses pages and extracts the
+first level list (wiki markup).
+
+@copyright: 2008 MoinMoin:ThomasWaldmann,
+            2008 MoinMoin:MelitaMihaljevic
+@license: GPL, see COPYING for details
+"""
+
+import re
+
+from MoinMoin import caching, wikiutil
+from MoinMoin.Page import Page
+
+class Group(object):
+    name = 'wiki'
+
+    # * Member - ignore all but first level list items, strip
+    # whitespace, strip free links markup. This is used for parsing
+    # pages in order to find group page members
+    group_page_parse_re = re.compile(ur'^ \* +(?:\[\[)?(?P<member>.+?)(?:\]\])? *$', re.MULTILINE | re.UNICODE)
+
+    def __init__(self, request, group_name):
+        """
+        Initialize a wiki group.
+
+        @parm request: request object
+        @parm group_name: group name (== group page name)
+        """
+        page = Page(request, group_name)
+        if page.exists():
+            arena = 'pagegroups'
+            key = wikiutil.quoteWikinameFS(group_name)
+            cache = caching.CacheEntry(request, arena, key, scope='wiki', use_pickle=True)
+            try:
+                cache_mtime = cache.mtime()
+                page_mtime = wikiutil.version2timestamp(page.mtime_usecs())
+                # TODO: fix up-to-date check mtime granularity problems
+                if cache_mtime > page_mtime:
+                    # cache is uptodate
+                    self.group = cache.content()
+                else:
+                    raise caching.CacheError
+            except caching.CacheError:
+                # either cache does not exist, is erroneous or not uptodate: recreate it
+                text = page.get_raw_body()
+                self.group = set(self._parse_page(text))
+                cache.update(self.group)
+        else:
+            # we have no such group
+            raise KeyError
+
+    def _parse_page(self, text):
+        """
+        Parse <group_name> page and return members.
+        """
+        return [match.group('member') for match in self.group_page_parse_re.finditer(text)]
+
+    def __contains__(self, name):
+        """
+        Check if name is a member of this group.
+        """
+        return name in self.group
+
+    def __iter__(self):
+        """
+        Iterate over group members.
+        """
+        return iter(self.group)
+
+
+class Backend(object):
+    name = 'wiki'
+
+    def __init__(self, request):
+        """
+        Create a group manager backend object.
+        """
+        self.request = request
+        self.page_group_regex = request.cfg.cache.page_group_regexact
+
+    def __contains__(self, group_name):
+        """
+        Check if there is group page <group_name>. <group_name> must satisfy page_group_regex.
+        """
+        return self.page_group_regex.match(group_name) and Page(self.request, group_name).exists()
+
+    def __iter__(self):
+        """
+        Iterate over group names of groups available in the wiki.
+        """
+        grouppages = self.request.rootpage.getPageList(user='', filter=self.page_group_regex.search)
+        return iter(grouppages)
+
+    def __getitem__(self, group_name):
+        """
+        Return wiki group backend object.
+        """
+        return Group(self.request, group_name)
+
--- a/MoinMoin/web/contexts.py	Sat Jun 06 11:48:29 2009 +0200
+++ b/MoinMoin/web/contexts.py	Sat Jun 06 11:48:40 2009 +0200
@@ -377,6 +377,13 @@
         return dicts
     dicts = EnvironProxy(dicts)
 
+    def groups(self):
+        """ Lazy initialize the groups on the first access """
+        groups = self.request.cfg.group_manager_init(self)
+        groups.load_cache()
+        return groups
+    groups = EnvironProxy(groups)
+
     def reset(self):
         self.current_lang = self.cfg.language_default
         if hasattr(self, '_fmt_hd_counters'):
--- a/docs/CHANGES.dmilajevs	Sat Jun 06 11:48:29 2009 +0200
+++ b/docs/CHANGES.dmilajevs	Sat Jun 06 11:48:40 2009 +0200
@@ -2,4 +2,5 @@
 
    New features:
    * Group backends. Group definitions can be stored outside of MoinMoin.
-   * MoinMoin.security.AccessControlList works with the new GroupManager.
\ No newline at end of file
+   * MoinMoin.security.AccessControlList works with the new GroupManager.
+