Commits

pkumar committed e7dfe66 Merge

merged default

Comments (0)

Files changed (41)

MoinMoin/_tests/test_test_environ.py

 from MoinMoin.conftest import init_test_app, deinit_test_app
 from MoinMoin.config import NAME, CONTENTTYPE, IS_SYSITEM, SYSITEM_VERSION
 from MoinMoin.storage.error import NoSuchItemError
-from MoinMoin.storage.serialization import serialize, unserialize
+from MoinMoin.storage.middleware.serialization import serialize, unserialize
 
 from MoinMoin._tests import wikiconfig
 

MoinMoin/_tests/test_user.py

         # "KeyError: 'enc_password'" error during user authentication.
         user_name = u'moin'
         user_password = u'{SHA}LKM56'
-        user.create_user(user_name, user_password, u'moin@moinmo.in', '')
+        user.create_user(user_name, user_password, u'moin@moinmo.in', u'')
 
         # Try to "login"
         theuser = user.User(name=user_name, password=user_password)
         theUser.subscribe(pagename)
         assert not theUser.isSubscribedTo([testPagename]) # list(!) of pages to check
 
-    def testRenameUser(self):
-        """ create user and then rename user and check if it still
-        exists under old name
-        """
-        # Create test user
-        name = u'__Some Name__'
-        password = name
-        self.createUser(name, password)
-        # Login - this should replace the old password in the user file
-        theUser = user.User(name=name)
-        # Rename user
-        theUser.name = u'__SomeName__'
-        theUser.save()
-        theUser = user.User(name=name, password=password)
-
-        assert not theUser.exists()
-
     def test_upgrade_password_from_ssha_to_ssha256(self):
         """
         Create user with {SSHA} password and check that logging in
         theuser = user.User(name=name)
         assert theuser.email is None
 
-    def test_for_email_attribut_by_uid(self):
-        """
-        checks access to the email attribute by getting the user object from the uid
-        """
-        name = u"__TestUser2__"
-        password = u"ekERErwerwerh"
-        email = u"__TestUser2__@moinhost"
-        self.createUser(name, password, email=email)
-        uid = user.getUserId(name)
-        theuser = user.User(uid)
-        assert theuser.email == email
-
     # Bookmarks -------------------------------------------------------
 
     def test_bookmark(self):
 
     # Other ----------------------------------------------------------
 
-    def test_signature(self):
-        name = u'Test_User_other'
-        password = name
-        self.createUser(name, password)
-        theUser = user.User(name=name, password=password)
-
-        # test the user signature
-        result = theUser.signature()
-        expected =  u'[[Test_User_other]]'
-        assert result == expected
-
     def test_recovery_token(self):
         name = u'Test_User_other'
         password = name

MoinMoin/_tests/test_wikiutil.py

 
 from MoinMoin import config, wikiutil
 from MoinMoin._tests import wikiconfig
-from MoinMoin.storage.serialization import serialize, unserialize
+from MoinMoin.storage.middleware.serialization import serialize, unserialize
 
 from werkzeug import MultiDict
 
 
 
 from MoinMoin.storage.error import StorageError
-from MoinMoin.storage.serialization import serialize, unserialize
-from MoinMoin.storage.backends import router, acl, memory
+from MoinMoin.storage.backends import memory
+from MoinMoin.storage.middleware.serialization import serialize, unserialize
+from MoinMoin.storage.middleware import router, acl
 from MoinMoin import auth, config, user
 
 
         # could have a param where the admin could tell whether he wants to
         # trust it)
         userobj.auth_trusted = userobj.auth_method in app.cfg.auth_methods_trusted
-        session['user.id'] = userobj.id
+        session['user.id'] = userobj.uuid
         session['user.auth_method'] = userobj.auth_method
         session['user.auth_attribs'] = userobj.auth_attribs
     return userobj

MoinMoin/apps/admin/views.py

 from MoinMoin.apps.admin import admin
 from MoinMoin import user
 from MoinMoin.storage.error import NoSuchRevisionError
-from MoinMoin.config import NAME, SIZE
+from MoinMoin.config import NAME, UUID, SIZE, EMAIL
 from MoinMoin.config import SUPERUSER
 from MoinMoin.security import require_permission
 
     User Account Browser
     """
     groups = flaskg.groups
-    user_accounts = []
-    for uid in user.getUserList():
-        u = user.User(uid)
-        user_accounts.append(dict(
-            uid=uid,
-            name=u.name,
-            email=u.email,
-            disabled=u.disabled,
-            groups=[groupname for groupname in groups if u.name in groups[groupname]],
-            ))
+    docs = user.search_users() # all users
+    user_accounts = [dict(uid=doc[UUID],
+                          name=doc[NAME],
+                          email=doc[EMAIL],
+                          disabled=False,  # TODO: add to index
+                          groups=[groupname for groupname in groups if doc[NAME] in groups[groupname]],
+                     )
+                     for doc in docs]
     return render_template('admin/userbrowser.html', user_accounts=user_accounts, item_name="+admin/Userbrowser")
 
 
     """
     Set values in user profile
     """
-    uid = user.getUserId(user_name)
-    u = user.User(uid)
+    u = user.User(auth_username=user_name)
     if request.method == 'GET':
         return _(u"User profile of %(username)s: %(email)r", username=user_name,
                  email=(u.email, u.disabled))
                 ok = False
         if ok:
             setattr(u, key, val)
-            theuser.save()
+            u.save()
             flash('%s.%s: %s -> %s' % (user_name, key, unicode(oldval), unicode(val), ), "info")
         else:
             flash('modifying %s.%s failed' % (user_name, key, ), "error")

MoinMoin/apps/frontend/views.py

         qp = flaskg.storage.query_parser([NAME_EXACT, NAME, CONTENT], all_revs=history)
         q = qp.parse(query)
         with flaskg.storage.searcher(all_revs=history) as searcher:
+            flaskg.clock.start('search')
             results = searcher.search(q, limit=100)
-            return render_template('search.html',
+            flaskg.clock.stop('search')
+            # XXX if found that calling key_terms like you see below is 1000..10000x
+            # slower than the search itself, so we better don't do that right now.
+            key_terms_is_fast = False
+            if key_terms_is_fast:
+                flaskg.clock.start('search suggestions')
+                name_suggestions = u', '.join([word for word, score in results.key_terms(NAME, docs=20, numterms=10)])
+                content_suggestions = u', '.join([word for word, score in results.key_terms(CONTENT, docs=20, numterms=10)])
+                flaskg.clock.stop('search suggestions')
+            else:
+                name_suggestions = u''
+                content_suggestions = u''
+            flaskg.clock.start('search render')
+            html = render_template('search.html',
                                    results=results,
-                                   name_suggestions=u', '.join([word for word, score in results.key_terms(NAME, docs=20, numterms=10)]),
-                                   content_suggestions=u', '.join([word for word, score in results.key_terms(CONTENT, docs=20, numterms=10)]),
+                                   name_suggestions=name_suggestions,
+                                   content_suggestions=content_suggestions,
                                    query=query,
                                    medium_search_form=search_form,
                                    item_name='+search', # XXX
                                   )
+            flaskg.clock.stop('search render')
     else:
-        return render_template('search.html',
+        html = render_template('search.html',
                                query=query,
                                medium_search_form=search_form,
                                item_name='+search', # XXX
                               )
-
+    return html
 
 
 @frontend.route('/<itemname:item_name>', defaults=dict(rev=-1), methods=['GET'])
             u = None
             username = form['username'].value
             if username:
-                u = user.User(user.getUserId(username))
+                u = user.User(auth_username=username)
             email = form['email'].value
             if form['email'].valid and email:
-                u = user.get_by_email_address(email)
+                users = user.search_users(email=email)
+                u = users and user.User(users[0][UUID])
             if u and u.valid:
                 is_ok, msg = u.mailAccountData()
                 if not is_ok:
     elif request.method == 'POST':
         form = PasswordRecoveryForm.from_flat(request.form)
         if form.validate():
-            u = user.User(user.getUserId(form['username'].value))
+            u = user.User(auth_username=form['username'].value)
             if u and u.valid and u.apply_recovery_token(form['token'].value, form['password1'].value):
                 flash(_("Your password has been changed, you can log in now."), "info")
             else:
                 flash(_("Your password has been changed."), "info")
             else:
                 if part == 'personal':
-                    if form['openid'].value != flaskg.user.openid and user.get_by_openid(form['openid'].value):
+                    if form['openid'].value != flaskg.user.openid and user.search_users(openid=form['openid'].value):
                         # duplicate openid
                         flash(_("This openid is already in use."), "error")
                         success = False
-                    if form['name'].value != flaskg.user.name and user.getUserId(form['name'].value):
+                    if form['name'].value != flaskg.user.name and user.search_users(name_exact=form['name'].value):
                         # duplicate name
                         flash(_("This username is already in use."), "error")
                         success = False
                 if part == 'notification':
                     if (form['email'].value != flaskg.user.email and
-                        user.get_by_email_address(form['email'].value) and app.cfg.user_email_unique):
+                        user.search_users(email=form['email'].value) and app.cfg.user_email_unique):
                         # duplicate email
                         flash(_('This email is already in use'), 'error')
                         success = False

MoinMoin/auth/_tests/test_auth.py

         givenauth_obj.strip_windomain = True
         givenauth_obj.titlecase = True
         givenauth_obj.remove_blanks = True
-        create_user('Test_User', 'test_pass', 'test@moinmoin.org')
+        create_user(u'Test_User', u'test_pass', u'test@moinmoin.org')
         test_user, bool_value = givenauth_obj.request(flaskg.user)
         assert test_user.valid
         assert test_user.name == u'Test_User'
 
 def test_handle_login():
-    # no messages in the biginning
+    # no messages in the beginning
     assert not flaskg._login_messages
     test_user1 = handle_login(flaskg.user, login_username = 'test_user', login_password = 'test_password', stage = 'moin')
     test_login_message = [u'Invalid username or password.']
     givenauth_obj = GivenAuth()
     flaskg.user.auth_method = 'given'
     givenauth_obj.user_name = u'Test_User'
-    create_user('Test_User', 'test_pass', 'test@moinmoin.org')
+    create_user(u'Test_User', u'test_pass', u'test@moinmoin.org')
     test_user, bool_value = givenauth_obj.request(flaskg.user)
     test_user2 = handle_login(test_user, login_username = 'Test_User', login_password = 'test_pass', stage = 'moin')
     assert not flaskg._login_messages

MoinMoin/auth/_tests/test_http.py

 
     def test_request(self):
         # create a new user
-        create_user(u'ValidUser', 'test_pass', 'test_email@moinmoin')
+        create_user(u'ValidUser', u'test_pass', u'test_email@moinmoin')
         httpauthmoin_obj = HTTPAuthMoin()
         test_user, bool_val = httpauthmoin_obj.request(flaskg.user)
         assert test_user.valid

MoinMoin/auth/openidrp.py

                 # we have successfully authenticated our openid
                 # we get the user with this openid associated to him
                 identity = oid_info.identity_url
-                user_obj = user.get_by_openid(identity)
+                users = user.search_users(openid=identity)
+                user_obj = users and user.User(users[0][UUID])
 
                 # if the user actually exists
                 if user_obj:

MoinMoin/config/__init__.py

 WIKINAME = "wikiname"
 CONTENT = "content"
 
+# stuff from user profiles / for whoosh index
+EMAIL = "email"
+OPENID = "openid"
+
 # structure for contenttype groups
 CONTENTTYPE_GROUPS = [
     ('markup text items', [

MoinMoin/converter/_tests/test_moinwiki_in_out.py

         for i in data:
             yield (self.do, ) + i
 
-    def test_variables(self):
-        data = [
-            (u"VAR:: text", u"VAR:: text"),
-            (u"@TIME@", u""),
-            (u"@DATE@", u""),
-            (u"@PAGE@", u""),
-        ]
-        for i in data:
-            yield (self.do, ) + i
-
     def test_link(self):
         data = [
             (u'[[SomePage#subsection|subsection of Some Page]]', '[[SomePage#subsection|subsection of Some Page]]\n'),

MoinMoin/converter/html_out.py

     This could remove +get or +modify for external links,
         when they shouldn't really be removed.
     """
-    return unicode(url).replace("+get/", "").replace("+modify/", "")
+    return unicode(url).replace("/+get/", "/+show/").replace("/+modify/", "/+show/")
 
 
 def wrap_object_with_overlay(elem, href):

MoinMoin/script/account/create.py

         if msg:
             print msg
         else:
-            uid = user.getUserId(name)
-            u = user.User(uid)
+            u = user.User(auth_username=name)
             print " %-20s %-25s %-35s - created." % (u.id, u.name, u.email),
 

MoinMoin/script/account/disable.py

         if uid:
             u = user.User(uid)
         elif name:
-            uid = user.getUserId(name)
-            u = user.User(uid)
+            u = user.User(auth_username=name)
 
         if not u.exists():
             print 'This user "%s" does not exists!' % u.name

MoinMoin/script/account/resetpw.py

         if uid:
             u = user.User(uid)
         elif name:
-            uid = user.getUserId(name)
-            u = user.User(uid)
+            u = user.User(auth_username=name)
 
         if not u.exists():
             print 'This user "%s" does not exists!' % u.name

MoinMoin/script/maint/index.py

 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
 from MoinMoin.util.mime import Type
 from MoinMoin.search.indexing import backend_to_index
-from MoinMoin.storage.backends.indexing import convert_to_indexable
+from MoinMoin.storage.middleware.indexing import convert_to_indexable
 
 from MoinMoin import log
 logging = log.getLogger(__name__)

MoinMoin/script/maint/xml.py

 
 from MoinMoin.script import fatal
 
-from MoinMoin.storage.serialization import unserialize, serialize, \
-                                           NLastRevs, SinceTime
+from MoinMoin.storage.middleware.serialization import unserialize, serialize, NLastRevs, SinceTime
 
 class XML(Command):
     description = "This command can be used to save items to a file or to create items by loading from a file"
 
         if moin19data:
             # this is for backend migration scenario from moin 1.9
-            from MoinMoin.storage.backends import create_simple_mapping, router
+            from MoinMoin.storage.backends import create_simple_mapping
+            from MoinMoin.storage.middleware import router
             namespace_mapping = create_simple_mapping(backend_uri='fs19:%s' % moin19data)
             storage = router.RouterBackend(
                     [(ns, be) for ns, be, acls in namespace_mapping], cfg=app.cfg)

MoinMoin/search/indexing.py

 
 from MoinMoin.config import WIKINAME, NAME, NAME_EXACT, REV_NO, MTIME, CONTENTTYPE, TAGS, \
                             LANGUAGE, USERID, ADDRESS, HOSTNAME, SIZE, ACTION, COMMENT, \
-                            CONTENT, UUID, ITEMLINKS, ITEMTRANSCLUSIONS, ACL
+                            CONTENT, UUID, ITEMLINKS, ITEMTRANSCLUSIONS, ACL, EMAIL, OPENID
 from MoinMoin.search.analyzers import *
 from MoinMoin.error import FatalError
 
         }
         latest_revs_fields.update(**common_fields)
 
+        userprofile_fields = {
+            # EMAIL from user profile metadata
+            EMAIL: ID(unique=True, stored=True),
+            # OPENID from user profile metadata
+            OPENID: ID(unique=True, stored=True),
+        }
+        latest_revs_fields.update(**userprofile_fields)
+
         all_revs_fields = {
             # UUID from metadata
             UUID: ID(stored=True),

MoinMoin/security/__init__.py

             that means that there is a valid user account present.
             works for subscription emails.
         """
-        if user.getUserId(name): # is a user with this name known?
+        if user.search_users(name_exact=name): # is a user with this name known?
             return rightsdict.get(dowhat)
         return None
 

MoinMoin/security/ticket.py

         # for age-check of ticket
         tm = "%010x" % time.time()
 
-    kw['uid'] = flaskg.user.valid and flaskg.user.id or ''
+    kw['uid'] = flaskg.user.valid and flaskg.user.uuid or ''
 
     hmac_data = []
     for value in sorted(kw.items()):

MoinMoin/storage/_tests/test_backends_fs2.py

 
 from MoinMoin.storage._tests.test_backends import BackendTest
 from MoinMoin.storage.backends.fs2 import FS2Backend
-from MoinMoin.storage.backends.router import RouterBackend
 
 class TestFS2Backend(BackendTest):
 

MoinMoin/storage/_tests/test_backends_hg.py

 from MoinMoin.storage.error import BackendError
 
 class TestMercurialBackend(BackendTest):
-    pytestmark = pytest.mark.xfail(reason='not maintained')
+    #pytestmark = pytest.mark.xfail(reason='not maintained')
 
     def create_backend(self):
         self.test_dir = mkdtemp()

MoinMoin/storage/_tests/test_backends_router.py

 from MoinMoin.error import ConfigurationError
 from MoinMoin.storage._tests.test_backends import BackendTest
 from MoinMoin.storage.backends.memory import MemoryBackend
-from MoinMoin.storage.backends.router import RouterBackend
+from MoinMoin.storage.middleware.router import RouterBackend
 from MoinMoin.search.indexing import WhooshIndex
 
 class TestRouterBackend(BackendTest):

MoinMoin/storage/_tests/test_indexing.py

 
 from MoinMoin._tests import update_item, nuke_item
 from MoinMoin._tests.wikiconfig import Config
-from MoinMoin.storage.backends.indexing import ItemIndex
+from MoinMoin.storage.middleware.indexing import ItemIndex
 from MoinMoin.config import NAME
 
 # Revisions for tests

MoinMoin/storage/_tests/test_serialization.py

 from flask import g as flaskg
 
 from MoinMoin._tests import become_trusted, update_item
-from MoinMoin.storage.serialization import Entry, create_value_object, serialize, unserialize
+from MoinMoin.storage.middleware.serialization import Entry, create_value_object, serialize, unserialize
 
 XML_DECL = '<?xml version="1.0" encoding="UTF-8"?>\n'
 

MoinMoin/storage/backends/__init__.py

 # Copyright: 2007 MoinMoin:HeinrichWendel
 # Copyright: 2008 MoinMoin:PawelPacana
 # Copyright: 2009 MoinMoin:ChristopherDenter
-# Copyright: 2009-2010 MoinMoin:ThomasWaldmann
+# Copyright: 2009-2011 MoinMoin:ThomasWaldmann
 # License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
 
 """
-    MoinMoin - Backends
-
-    This package contains code for the backends of the new storage layer.
+MoinMoin - Storage Backends
 """
 
 
 from flask import current_app as app
 from flask import g as flaskg
 
-from MoinMoin.storage.serialization import unserialize
 from MoinMoin.storage.error import NoSuchItemError, RevisionAlreadyExistsError
 from MoinMoin.error import ConfigurationError
-from MoinMoin.storage.backends import router, fs, fs2, fs19, memory
+from MoinMoin.storage.backends import fs, fs2, fs19, memory
+from MoinMoin.storage.middleware import router
+from MoinMoin.storage.middleware.serialization import unserialize
 
 CONTENT = 'content'
 USERPROFILES = 'userprofiles'

MoinMoin/storage/backends/acl.py

-# Copyright: 2003-2011 MoinMoin:ThomasWaldmann
-# Copyright: 2000-2004 Juergen Hermann <jh@web.de>
-# Copyright: 2003 Gustavo Niemeyer
-# Copyright: 2005 Oliver Graf
-# Copyright: 2007 Alexander Schremmer
-# Copyright: 2009 Christopher Denter
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-MoinMoin - ACL Middleware
-
-This backend is a middleware implementing access control using ACLs (access
-control lists) and is referred to as AMW (ACL MiddleWare) hereafter.
-It does not store any data, but uses a given backend for this.
-This middleware is injected between the user of the storage API and the actual
-backend used for storage. It is independent of the backend being used.
-Instances of the AMW are bound to individual request objects. The user whose
-permissions the AMW checks is hence obtained by a lookup on the request object.
-The backend itself (and the objects it returns) need to be wrapped in order
-to make sure that no object of the real backend is (directly or indirectly)
-made accessible to the user of the API.
-The real backend is still available as an attribute of the request and can
-be used by conversion utilities or for similar tasks (flaskg.unprotected_storage).
-Regular users of the storage API, such as the views that modify an item,
-*MUST NOT*, in any way, use the real backend unless the author knows *exactly*
-what he's doing (as this may introduce security bugs without the code actually
-being broken).
-
-The classes wrapped are:
-    * AclWrapperBackend (wraps MoinMoin.storage.Backend)
-    * AclWrapperItem (wraps MoinMoin.storage.Item)
-    * AclWrapperRevision (wraps MoinMoin.storage.Revision)
-
-When an attribute is 'wrapped' it means that, in this context, the user's
-permissions are checked prior to attribute usage. If the user may not perform
-the action he intended to perform, an AccessDeniedError is raised.
-Otherwise the action is performed on the respective attribute of the real backend.
-It is important to note here that the outcome of such an action may need to
-be wrapped itself, as is the case when items or revisions are returned.
-
-All wrapped classes must, of course, adhere to the normal storage API.
-"""
-
-
-from UserDict import DictMixin
-
-from flask import current_app as app
-from flask import g as flaskg
-
-from MoinMoin.security import AccessControlList
-
-from MoinMoin.storage import Item, NewRevision, StoredRevision
-from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, AccessDeniedError
-
-from MoinMoin.config import ACL, ADMIN, READ, WRITE, CREATE, DESTROY
-
-
-class AclWrapperBackend(object):
-    """
-    The AMW is bound to a specific request. The actual backend is retrieved
-    from the config upon request initialization. Any method that is in some
-    way relevant to security needs to be wrapped in order to ensure the user
-    has the permissions necessary to perform the desired action.
-    Note: This may *not* inherit from MoinMoin.storage.Backend because that would
-    break our __getattr__ attribute 'redirects' (which are necessary because a backend
-    implementor may decide to use his own helper functions which the items and revisions
-    will still try to call).
-    """
-    def __init__(self, cfg, backend, hierarchic=False, before=u"", default=u"", after=u"", valid=None):
-        """
-        :type backend: Some object that implements the storage API.
-        :param backend: The unprotected backend that we want to protect.
-        :type hierarchic: bool
-        :param hierarchic: Indicate whether we want to process ACLs in hierarchic mode.
-        :type before: unicode
-        :param before: ACL to be applied before all the other ACLs.
-        :type default: unicode
-        :param default: If no ACL information is given on the item in question, use this default.
-        :type after: unicode
-        :param after: ACL to be applied after all the other ACLs.
-        :type valid: list of strings or None
-        :param valid: If a list is given, only strings in the list are treated as valid acl privilege descriptors.
-                      If None is give, the global wiki default is used.
-        """
-        self.cfg = cfg
-        self.backend = backend
-        self.hierarchic = hierarchic
-        self.valid = valid if valid is not None else cfg.acl_rights_contents
-        self.before = AccessControlList([before], default=default, valid=self.valid)
-        self.default = AccessControlList([default], default=default, valid=self.valid)
-        self.after = AccessControlList([after], default=default, valid=self.valid)
-
-    def __getattr__(self, attr):
-        # Attributes that this backend does not define itself are just looked
-        # up on the real backend.
-        return getattr(self.backend, attr)
-
-    def get_item(self, itemname):
-        """
-        @see: Backend.get_item.__doc__
-        """
-        if not self._may(itemname, READ):
-            raise AccessDeniedError(flaskg.user.name, READ, itemname)
-        real_item = self.backend.get_item(itemname)
-        # Wrap the item here as well.
-        wrapped_item = AclWrapperItem(real_item, self)
-        return wrapped_item
-
-    def has_item(self, itemname):
-        """
-        @see: Backend.has_item.__doc__
-        """
-        # We do not hide the sheer existance of items. When trying
-        # to create an item with the same name, the user would notice anyway.
-        return self.backend.has_item(itemname)
-
-    def create_item(self, itemname):
-        """
-        @see: Backend.create_item.__doc__
-        """
-        if not self._may(itemname, CREATE):
-            raise AccessDeniedError(flaskg.user.name, CREATE, itemname)
-        real_item = self.backend.create_item(itemname)
-        # Wrap item.
-        wrapped_item = AclWrapperItem(real_item, self)
-        return wrapped_item
-
-    def iter_items_noindex(self):
-        """
-        @see: Backend.iter_items_noindex.__doc__
-        """
-        for item in self.backend.iteritems():
-            if self._may(item.name, READ):
-                yield AclWrapperItem(item, self)
-
-    iteritems = iter_items_noindex
-
-    def _get_acl(self, itemname):
-        """
-        Get ACL strings from the last revision's metadata and return ACL object.
-        """
-        try:
-            item = self.backend.get_item(itemname)
-            # we always use the ACLs set on the latest revision:
-            current_rev = item.get_revision(-1)
-            acl = current_rev[ACL]
-            if not isinstance(acl, unicode):
-                raise TypeError("%s metadata has unsupported type: %r" % (ACL, acl))
-            acls = [acl, ]
-        except (NoSuchItemError, NoSuchRevisionError, KeyError):
-            # do not use default acl here
-            acls = []
-        default = self.default.default
-        return AccessControlList(tuple(acls), default=default, valid=self.valid)
-
-    def _may(self, itemname, right, username=None):
-        """ Check if username may have <right> access on item <itemname>.
-
-        For hierarchic=False we just check the item in question.
-
-        For hierarchic=True, we check each item in the hierarchy. We
-        start with the deepest item and recurse to the top of the tree.
-        If one of those permits, True is returned.
-        This is done *only* if there is *no ACL at all* (not even an empty one)
-        on the items we 'recurse over'.
-
-        For both configurations, we check `before` before the item/default
-        acl and `after` after the item/default acl, of course.
-
-        `default` is only used if there is no ACL on the item (and none on
-        any of the item's parents when using hierarchic.)
-
-        :param itemname: item to get permissions from
-        :param right: the right to check
-        :param username: username to use for permissions check (default is to
-                         use the username doing the current request)
-        :rtype: bool
-        :returns: True if you have permission or False
-        """
-        if username is None:
-            username = flaskg.user.name
-
-        allowed = self.before.may(username, right)
-        if allowed is not None:
-            return allowed
-
-        if self.hierarchic:
-            items = itemname.split('/') # create item hierarchy list
-            some_acl = False
-            for i in range(len(items), 0, -1):
-                # Create the next pagename in the hierarchy
-                # starting at the leaf, going to the root
-                name = '/'.join(items[:i])
-                acl = self._get_acl(name)
-                if acl.has_acl():
-                    some_acl = True
-                    allowed = acl.may(username, right)
-                    if allowed is not None:
-                        return allowed
-                    # If the item has an acl (even one that doesn't match) we *do not*
-                    # check the parents. We only check the parents if there's no acl on
-                    # the item at all.
-                    break
-            if not some_acl:
-                allowed = self.default.may(username, right)
-                if allowed is not None:
-                    return allowed
-        else:
-            acl = self._get_acl(itemname)
-            if acl.has_acl():
-                allowed = acl.may(username, right)
-                if allowed is not None:
-                    return allowed
-            else:
-                allowed = self.default.may(username, right)
-                if allowed is not None:
-                    return allowed
-
-        allowed = self.after.may(username, right)
-        if allowed is not None:
-            return allowed
-
-        return False
-
-
-class AclWrapperItem(Item):
-    """
-    Similar to AclWrapperBackend. Wrap a storage item and protect its
-    attributes by performing permission checks prior to performing the
-    action and raising AccessDeniedErrors if appropriate.
-    """
-    def __init__(self, item, aclbackend):
-        """
-        :type item: Object adhering to the storage item API.
-        :param item: The unprotected item we want to wrap.
-        :type aclbackend: Instance of AclWrapperBackend.
-        :param aclbackend: The AMW this item belongs to.
-        """
-        self._backend = aclbackend
-        self._item = item
-        self._may = aclbackend._may
-
-    @property
-    def name(self):
-        """
-        @see: Item.name.__doc__
-        """
-        return self._item.name
-
-    # needed by storage.serialization:
-    @property
-    def element_name(self):
-        return self._item.element_name
-    @property
-    def element_attrs(self):
-        return self._item.element_attrs
-
-    def require_privilege(*privileges):
-        """
-        This decorator is used in order to avoid code duplication
-        when checking a user's permissions. It allows providing arguments
-        that represent the permissions to check, such as READ and WRITE
-        (see module level constants; don't pass strings, please).
-
-        :type privileges: List of strings.
-        :param privileges: Represent the privileges to check.
-        """
-        def wrap(f):
-            def wrapped_f(self, *args, **kwargs):
-                for privilege in privileges:
-                    if not self._may(self.name, privilege):
-                        username = flaskg.user.name
-                        raise AccessDeniedError(username, privilege, self.name)
-                return f(self, *args, **kwargs)
-            return wrapped_f
-        return wrap
-
-
-    @require_privilege(WRITE)
-    def __setitem__(self, key, value):
-        """
-        @see: Item.__setitem__.__doc__
-        """
-        return self._item.__setitem__(key, value)
-
-    @require_privilege(WRITE)
-    def __delitem__(self, key):
-        """
-        @see: Item.__delitem__.__doc__
-        """
-        return self._item.__delitem__(key)
-
-    @require_privilege(READ)
-    def __getitem__(self, key):
-        """
-        @see: Item.__getitem__.__doc__
-        """
-        return self._item.__getitem__(key)
-
-    @require_privilege(READ)
-    def keys(self):
-        """
-        @see: Item.keys.__doc__
-        """
-        return self._item.keys()
-
-    @require_privilege(WRITE)
-    def change_metadata(self):
-        """
-        @see: Item.change_metadata.__doc__
-        """
-        return self._item.change_metadata()
-
-    @require_privilege(WRITE)
-    def publish_metadata(self):
-        """
-        @see: Item.publish_metadata.__doc__
-        """
-        return self._item.publish_metadata()
-
-    @require_privilege(READ)
-    def get_revision(self, revno):
-        """
-        @see: Item.get_revision.__doc__
-        """
-        return AclWrapperRevision(self._item.get_revision(revno), self)
-
-    @require_privilege(READ)
-    def list_revisions(self):
-        """
-        @see: Item.list_revisions.__doc__
-        """
-        return self._item.list_revisions()
-
-    @require_privilege(READ, WRITE)
-    def rename(self, newname):
-        """
-        Rename item from name (src) to newname (dst).
-        Note that there is no special rename privilege. By taking other
-        privileges into account, we implicitly perform the permission check here.
-        This checks R/W at src and W/C at dst. This combination was chosen for
-        the following reasons:
-         * It is the most intuitive of the possible solutions.
-         * If we'd only check for R at src, everybody would be able to rename even
-           ImmutablePages if there is a writable/creatable name somewhere else
-           (e.g., Trash/).
-         * 'delete' aka 'rename to trashbin' can be controlled with 'create':
-           Just don't provide create for the trash namespace.
-         * Someone without create in the target namespace cannot rename.
-
-        @see: Item.rename.__doc__
-        """
-        # Special case since we need to check newname as well. Easier to special-case than
-        # adjusting the decorator.
-        username = flaskg.user.name
-        if not self._may(newname, CREATE):
-            raise AccessDeniedError(username, CREATE, newname)
-        if not self._may(newname, WRITE):
-            raise AccessDeniedError(username, WRITE, newname)
-        return self._item.rename(newname)
-
-    @require_privilege(WRITE)
-    def commit(self):
-        """
-        @see: Item.commit.__doc__
-        """
-        return self._item.commit()
-
-    # This does not require a privilege as the item must have been obtained
-    # by either get_item or create_item already, which already check permissions.
-    def rollback(self):
-        """
-        @see: Item.rollback.__doc__
-        """
-        return self._item.rollback()
-
-    @require_privilege(DESTROY)
-    def destroy(self):
-        """
-        USE WITH GREAT CARE!
-
-        @see: Item.destroy.__doc__
-        """
-        return self._item.destroy()
-
-    @require_privilege(WRITE)
-    def create_revision(self, revno):
-        """
-        @see: Item.create_revision.__doc__
-        """
-        wrapped_revision = AclWrapperRevision(self._item.create_revision(revno), self)
-        return wrapped_revision
-
-
-class AclWrapperRevision(object, DictMixin):
-    """
-    Wrapper for revision classes. We need to wrap NewRevisions because they allow altering data.
-    We need to wrap StoredRevisions since they offer a destroy() method and access to their item.
-    The caller should know what kind of revision he gets. Hence, we just implement the methods of
-    both, StoredRevision and NewRevision. If a method is invoked that is not defined on the
-    kind of revision we wrap, we will see an AttributeError one level deeper anyway, so this is ok.
-    """
-    def __init__(self, revision, item):
-        """
-        :type revision: Object adhering to the storage revision API.
-        :param revision: The revision we want to protect.
-        :type item: Object adhering to the storage item API.
-        :param item: The item this revision belongs to
-        """
-        self._revision = revision
-        self._item = item
-        self._may = item._may
-
-    def __getattr__(self, attr):
-        # Pass through any call that is not subject to ACL protection (e.g. serialize)
-        return getattr(self._revision, attr)
-
-    @property
-    def item(self):
-        """
-        @see: Revision.item.__doc__
-        """
-        return self._item
-
-    @property
-    def timestamp(self):
-        """This property accesses the creation timestamp of the revision"""
-        return self._revision.timestamp
-
-    def __setitem__(self, key, value):
-        """
-        In order to change an ACL on an item you must have the ADMIN privilege.
-        We must allow the (unchanged) preceeding revision's ACL being stored
-        into the new revision, though.
-
-        TODO: the ACL specialcasing done here (requiring admin privilege for
-              changing ACLs) is only one case of a more generic problem:
-              Access (read,write,change) to some metadata must be checked.
-              ACL - changing needs ADMIN priviledge
-              userid, ip, hostname, etc. - writing them should be from system only
-              content hash - writing it should be from system only
-              For the metadata editing offered to the wiki user on the UI,
-              we should only offer metadata for which the wiki user has change
-              permissions. On save, we have to check the permissions.
-              Idea: have metadata key prefixes, classifying metadata entries:
-              security.* - security related
-                      .acl - content acl
-                      .insecure - allow insecure rendering (e.g. raw html)
-              system.* - internal stuff, only system may process this
-              user.* - user defined entries
-              (... needs more thinking ...)
-
-        @see: NewRevision.__setitem__.__doc__
-        """
-        if key == ACL:
-            try:
-                # This rev is not yet committed
-                last_rev = self._item.get_revision(-1)
-                last_acl = last_rev[ACL]
-            except (NoSuchRevisionError, KeyError):
-                last_acl = u''
-
-            acl_changed = value != last_acl
-
-            if acl_changed and not self._may(self._item.name, ADMIN):
-                username = flaskg.user.name
-                raise AccessDeniedError(username, ADMIN, self._item.name)
-        return self._revision.__setitem__(key, value)
-
-    def __getitem__(self, key):
-        """
-        @see: NewRevision.__getitem__.__doc__
-        """
-        return self._revision[key]
-
-    def __delitem__(self, key):
-        """
-        @see: NewRevision.__delitem__.__doc__
-        """
-        del self._revision[key]
-
-    def read(self, chunksize=-1):
-        """
-        @see: Backend._read_revision_data.__doc__
-        """
-        return self._revision.read(chunksize)
-
-    def seek(self, position, mode=0):
-        """
-        @see: StringIO.StringIO().seek.__doc__
-        """
-        return self._revision.seek(position, mode)
-
-    def destroy(self):
-        """
-        @see: Backend._destroy_revision.__doc__
-        """
-        if not self._may(self._item.name, DESTROY):
-            username = flaskg.user.name
-            raise AccessDeniedError(username, DESTROY + " revisions of", self._item.name)
-        return self._revision.destroy()
-
-    def write(self, data):
-        """
-        @see: Backend._write_revision_data.__doc__
-        """
-        return self._revision.write(data)
-

MoinMoin/storage/backends/fs19.py

 from StringIO import StringIO
 import hashlib
 
-from uuid import uuid4
-make_uuid = lambda: unicode(uuid4().hex)
-
 MAX_NAME_LEN = 1000 # max length of a page name, page+attach name, user name
-UUID_LEN = len(make_uuid())
 
 from sqlalchemy import create_engine, MetaData, Table, Column, String, Unicode, Integer
 
 from MoinMoin.storage.backends._flatutils import split_body
 from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError
 from MoinMoin.util.mimetype import MimeType
+from MoinMoin.util.crypto import make_uuid, UUID_LEN
 
 
 DELETED_MODE_KEEP = 'keep'

MoinMoin/storage/backends/fs2.py

 
 
 import os, tempfile, errno, shutil
-from uuid import uuid4 as make_uuid
 
 import cPickle as pickle
 
 
 from MoinMoin.util.lock import ExclusiveLock
 from MoinMoin.util import filesys
+from MoinMoin.util.crypto import make_uuid, UUID_LEN
 
 from MoinMoin.storage import Backend as BackendBase
 from MoinMoin.storage import Item as ItemBase
 MAX_NAME_LEN = 500
 from MoinMoin.config import HASH_ALGORITHM
 
-UUID_LEN = len(make_uuid().hex)
-
 
 class Item(ItemBase):
     def __init__(self, backend, item_name, _fs_item_id=None, _fs_metadata=None, *args, **kw):
         See _add_item_internally, this is just internal for locked operation.
         """
         item, revmeta, revdata, revdata_target, itemmeta = arg
-        item_id = make_uuid().hex
+        item_id = make_uuid()
         item_name = item.name
 
         name2id = self._name2id

MoinMoin/storage/backends/indexing.py

-# Copyright: 2010-2011 MoinMoin:ThomasWaldmann
-# Copyright: 2011 MoinMoin:MichaelMayorov
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - Indexing Mixin Classes
-
-    Other backends mix in the Indexing*Mixin classes into their Backend,
-    Item, Revision classes to support flexible metadata indexing and querying
-    for wiki items / revisions
-
-    Wiki items and revisions of same item are identified by same UUID.
-    The wiki item name is contained in the item revision's metadata.
-    If you rename an item, this is done by creating a new revision with a different
-    (new) name in its revision metadata.
-"""
-
-
-import os
-import time, datetime
-
-from uuid import uuid4
-make_uuid = lambda: unicode(uuid4().hex)
-
-from flask import current_app as app
-from flask import g as flaskg
-from flask import request
-
-from MoinMoin.storage.error import NoSuchItemError, NoSuchRevisionError, \
-                                   AccessDeniedError
-from MoinMoin.config import ACL, CONTENTTYPE, UUID, NAME, NAME_OLD, MTIME, TAGS, \
-                            ADDRESS, HOSTNAME, USERID, ITEMLINKS, ITEMTRANSCLUSIONS, \
-                            REV_NO
-from MoinMoin.search.indexing import backend_to_index
-from MoinMoin.converter import default_registry
-from MoinMoin.util.iri import Iri
-from MoinMoin.util.mime import Type, type_moin_document
-from MoinMoin.util.tree import moin_page
-from MoinMoin import wikiutil
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-
-def convert_to_indexable(rev, new_rev=False):
-    """
-    convert a revision to an indexable document
-
-    :param rev: item revision - please make sure that the content file is
-                ready to read all indexable content from it. if you have just
-                written that content or already read from it, you need to call
-                rev.seek(0) before calling convert_to_indexable(rev).
-    """
-    try:
-        # TODO use different converter mode?
-        # Maybe we want some special mode for the input converters so they emit
-        # different output than for normal rendering), esp. for the non-markup
-        # content types (images, etc.).
-        input_contenttype = rev[CONTENTTYPE]
-        output_contenttype = 'text/plain'
-        type_input_contenttype = Type(input_contenttype)
-        type_output_contenttype = Type(output_contenttype)
-        reg = default_registry
-        # first try a direct conversion (this could be useful for extraction
-        # of (meta)data from binary types, like from images or audio):
-        conv = reg.get(type_input_contenttype, type_output_contenttype)
-        if conv:
-            doc = conv(rev, input_contenttype)
-            return doc
-        # otherwise try via DOM as intermediate format (this is useful if
-        # input type is markup, to get rid of the markup):
-        input_conv = reg.get(type_input_contenttype, type_moin_document)
-        refs_conv = reg.get(type_moin_document, type_moin_document, items='refs')
-        output_conv = reg.get(type_moin_document, type_output_contenttype)
-        if input_conv and output_conv:
-            doc = input_conv(rev, input_contenttype)
-            # We do not convert smileys, includes, macros, links, because
-            # it does not improve search results or even makes results worse.
-            # We do run the referenced converter, though, to extract links and
-            # transclusions.
-            if new_rev:
-                # we only can modify new, uncommitted revisions, not stored revs
-                i = Iri(scheme='wiki', authority='', path='/' + rev[NAME])
-                doc.set(moin_page.page_href, unicode(i))
-                refs_conv(doc)
-                # side effect: we update some metadata:
-                rev[ITEMLINKS] = refs_conv.get_links()
-                rev[ITEMTRANSCLUSIONS] = refs_conv.get_transclusions()
-            doc = output_conv(doc)
-            return doc
-        # no way
-        raise TypeError("No converter for %s --> %s" % (input_contenttype, output_contenttype))
-    except Exception as e: # catch all exceptions, we don't want to break an indexing run
-        logging.exception("Exception happened in conversion of item %r rev %d contenttype %s:" % (rev[NAME], rev.revno, rev[CONTENTTYPE]))
-        doc = u'ERROR [%s]' % str(e)
-        return doc
-
-
-class IndexingBackendMixin(object):
-    """
-    Backend indexing support / functionality using the index.
-    """
-    def __init__(self, *args, **kw):
-        cfg = kw.pop('cfg')
-        super(IndexingBackendMixin, self).__init__(*args, **kw)
-        self._index = ItemIndex(cfg)
-
-    def close(self):
-        self._index.close()
-        super(IndexingBackendMixin, self).close()
-
-    def create_item(self, itemname):
-        """
-        intercept new item creation and make sure there is NAME / UUID in the item
-        """
-        item = super(IndexingBackendMixin, self).create_item(itemname)
-        item.change_metadata()
-        if NAME not in item:
-            item[NAME] = itemname
-        if UUID not in item:
-            item[UUID] = make_uuid()
-        item.publish_metadata()
-        return item
-
-    def query_parser(self, default_fields, all_revs=False):
-        return self._index.query_parser(default_fields, all_revs=all_revs)
-
-    def searcher(self, all_revs=False):
-        return self._index.searcher(all_revs=all_revs)
-
-    def search(self, q, all_revs=False, **kw):
-        return self._index.search(q, all_revs=all_revs, **kw)
-
-    def search_page(self, q, all_revs=False, pagenum=1, pagelen=10, **kw):
-        return self._index.search_page(q, all_revs=all_revs, pagenum=pagenum, pagelen=pagelen, **kw)
-
-    def documents(self, all_revs=False, **kw):
-        return self._index.documents(all_revs=all_revs, **kw)
-
-
-class IndexingItemMixin(object):
-    """
-    Item indexing support
-    """
-    def __init__(self, backend, *args, **kw):
-        super(IndexingItemMixin, self).__init__(backend, *args, **kw)
-        self._index = backend._index
-        self.__unindexed_revision = None
-
-    def create_revision(self, revno):
-        self.__unindexed_revision = super(IndexingItemMixin, self).create_revision(revno)
-        return self.__unindexed_revision
-
-    def commit(self):
-        self.__unindexed_revision.update_index()
-        self.__unindexed_revision = None
-        return super(IndexingItemMixin, self).commit()
-
-    def rollback(self):
-        self.__unindexed_revision = None
-        return super(IndexingItemMixin, self).rollback()
-
-    def publish_metadata(self):
-        self.update_index()
-        return super(IndexingItemMixin, self).publish_metadata()
-
-    def destroy(self):
-        self.remove_index()
-        return super(IndexingItemMixin, self).destroy()
-
-    def update_index(self):
-        """
-        update the index with metadata of this item
-
-        this is automatically called by item.publish_metadata() and can be used by a indexer script also.
-        """
-        logging.debug("item %r update index:" % (self.name, ))
-        for k, v in self.items():
-            logging.debug(" * item meta %r: %r" % (k, v))
-        self._index.update_item(metas=self)
-
-    def remove_index(self):
-        """
-        update the index, removing everything related to this item
-        """
-        uuid = self[UUID]
-        logging.debug("item %r %r remove index!" % (self.name, uuid))
-        self._index.remove_item(uuid)
-
-
-class IndexingRevisionMixin(object):
-    """
-    Revision indexing support
-    """
-    def __init__(self, item, *args, **kw):
-        super(IndexingRevisionMixin, self).__init__(item, *args, **kw)
-        self._index = item._index
-
-    def destroy(self):
-        self.remove_index()
-        return super(IndexingRevisionMixin, self).destroy()
-
-    def update_index(self):
-        """
-        update the index with metadata of this revision
-
-        this is automatically called by item.commit() and can be used by a indexer script also.
-        """
-        name = self.item.name
-        uuid = self.item[UUID]
-        revno = self.revno
-        logging.debug("Processing: name %s revno %s" % (name, revno))
-        if MTIME not in self:
-            self[MTIME] = int(time.time())
-        if NAME not in self:
-            self[NAME] = name
-        if UUID not in self:
-            self[UUID] = uuid # do we want the item's uuid in the rev's metadata?
-        if CONTENTTYPE not in self:
-            self[CONTENTTYPE] = u'application/octet-stream'
-
-        if app.cfg.log_remote_addr:
-            remote_addr = request.remote_addr
-            if remote_addr:
-                self[ADDRESS] = unicode(remote_addr)
-                hostname = wikiutil.get_hostname(remote_addr)
-                if hostname:
-                    self[HOSTNAME] = hostname
-        try:
-            if flaskg.user.valid:
-                self[USERID] = unicode(flaskg.user.id)
-        except:
-            # when loading xml via script, we have no flaskg.user
-            pass
-
-        self.seek(0) # for a new revision, file pointer points to EOF, rewind first
-        rev_content = convert_to_indexable(self, new_rev=True)
-
-        logging.debug("item %r revno %d update index:" % (name, revno))
-        for k, v in self.items():
-            logging.debug(" * rev meta %r: %r" % (k, v))
-        logging.debug("Indexable content: %r" % (rev_content[:250], ))
-        self._index.add_rev(uuid, revno, self, rev_content)
-
-    def remove_index(self):
-        """
-        update the index, removing everything related to this revision
-        """
-        name = self.item.name
-        uuid = self.item[UUID]
-        revno = self.revno
-        metas = self
-        logging.debug("item %r revno %d remove index!" % (name, revno))
-        self._index.remove_rev(metas[UUID], revno)
-
-    # TODO maybe use this class later for data indexing also,
-    # TODO by intercepting write() to index data written to a revision
-
-from whoosh.writing import AsyncWriter
-from whoosh.qparser import QueryParser, MultifieldParser
-
-from MoinMoin.search.indexing import WhooshIndex
-
-class ItemIndex(object):
-    """
-    Index for Items/Revisions
-    """
-    def __init__(self, cfg, force_create=False):
-        self.wikiname = cfg.interwikiname
-        self.index_object = WhooshIndex(force_create=force_create, cfg=cfg)
-
-    def close(self):
-        self.index_object.all_revisions_index.close()
-        self.index_object.latest_revisions_index.close()
-
-    def remove_index(self):
-        self.index_object.remove_index()
-
-    def update_item(self, metas):
-        """
-        update item (not revision!) metadata
-        """
-        # XXX we do not have an index for item metadata yet!
-
-    def remove_item(self, uuid):
-        """
-        remove all data related to this item and all its revisions from the index
-        """
-        with self.index_object.latest_revisions_index.searcher() as latest_revs_searcher:
-            doc_number = latest_revs_searcher.document_number(uuid=uuid,
-                                                              wikiname=self.wikiname
-                                                             )
-        if doc_number is not None:
-            with AsyncWriter(self.index_object.latest_revisions_index) as async_writer:
-                async_writer.delete_document(doc_number)
-
-        with self.index_object.all_revisions_index.searcher() as all_revs_searcher:
-            doc_numbers = list(all_revs_searcher.document_numbers(uuid=uuid,
-                                                                  wikiname=self.wikiname
-                                                                 ))
-        if doc_numbers:
-            with AsyncWriter(self.index_object.all_revisions_index) as async_writer:
-                for doc_number in doc_numbers:
-                    async_writer.delete_document(doc_number)
-
-    def add_rev(self, uuid, revno, rev, rev_content):
-        """
-        add a new revision <revno> for item <uuid> with metadata <metas>
-        """
-        with self.index_object.all_revisions_index.searcher() as all_revs_searcher:
-            all_found_document = all_revs_searcher.document(uuid=rev[UUID],
-                                                            rev_no=revno,
-                                                            wikiname=self.wikiname
-                                                           )
-        with self.index_object.latest_revisions_index.searcher() as latest_revs_searcher:
-            latest_found_document = latest_revs_searcher.document(uuid=rev[UUID],
-                                                                  wikiname=self.wikiname
-                                                                 )
-        if not all_found_document:
-            schema = self.index_object.all_revisions_index.schema
-            with AsyncWriter(self.index_object.all_revisions_index) as async_writer:
-                converted_rev = backend_to_index(rev, revno, schema, rev_content, self.wikiname)
-                logging.debug("All revisions: adding %s %s", converted_rev[NAME], converted_rev[REV_NO])
-                async_writer.add_document(**converted_rev)
-        if not latest_found_document or int(revno) > latest_found_document[REV_NO]:
-            schema = self.index_object.latest_revisions_index.schema
-            with AsyncWriter(self.index_object.latest_revisions_index) as async_writer:
-                converted_rev = backend_to_index(rev, revno, schema, rev_content, self.wikiname)
-                logging.debug("Latest revisions: updating %s %s", converted_rev[NAME], converted_rev[REV_NO])
-                async_writer.update_document(**converted_rev)
-
-    def remove_rev(self, uuid, revno):
-        """
-        remove a revision <revno> of item <uuid>
-        """
-        with self.index_object.latest_revisions_index.searcher() as latest_revs_searcher:
-            latest_doc_number = latest_revs_searcher.document_number(uuid=uuid,
-                                                                     rev_no=revno,
-                                                                     wikiname=self.wikiname
-                                                                    )
-        if latest_doc_number is not None:
-            with AsyncWriter(self.index_object.latest_revisions_index) as async_writer:
-                logging.debug("Latest revisions: removing %d", latest_doc_number)
-                async_writer.delete_document(latest_doc_number)
-
-        with self.index_object.all_revisions_index.searcher() as all_revs_searcher:
-            doc_number = all_revs_searcher.document_number(uuid=uuid,
-                                                           rev_no=revno,
-                                                           wikiname=self.wikiname
-                                                          )
-        if doc_number is not None:
-            with AsyncWriter(self.index_object.all_revisions_index) as async_writer:
-                logging.debug("All revisions: removing %d", doc_number)
-                async_writer.delete_document(doc_number)
-
-    def query_parser(self, default_fields, all_revs=False):
-        if all_revs:
-            schema = self.index_object.all_revisions_schema
-        else:
-            schema = self.index_object.latest_revisions_schema
-        if len(default_fields) > 1:
-            qp = MultifieldParser(default_fields, schema=schema)
-        elif len(default_fields) == 1:
-            qp = QueryParser(default_fields[0], schema=schema)
-        else:
-            raise ValueError("default_fields list must at least contain one field name")
-        return qp
-
-    def searcher(self, all_revs=False):
-        """
-        Get a searcher for the right index. Always use this with "with":
-
-        with storage.searcher(all_revs) as searcher:
-            # your code
-
-        If you do not need the searcher itself or the Result object, but rather
-        the found documents, better use search() or search_page(), see below.
-        """
-        if all_revs:
-            ix = self.index_object.all_revisions_index
-        else:
-            ix = self.index_object.latest_revisions_index
-        return ix.searcher()
-
-    def search(self, q, all_revs=False, **kw):
-        with self.searcher(all_revs) as searcher:
-            # Note: callers must consume everything we yield, so the for loop
-            # ends and the "with" is left to close the index files.
-            for hit in searcher.search(q, **kw):
-                yield hit.fields()
-
-    def search_page(self, q, all_revs=False, pagenum=1, pagelen=10, **kw):
-        with self.searcher(all_revs) as searcher:
-            # Note: callers must consume everything we yield, so the for loop
-            # ends and the "with" is left to close the index files.
-            for hit in searcher.search_page(q, pagenum, pagelen=pagelen, **kw):
-                yield hit.fields()
-
-    def documents(self, all_revs=False, **kw):
-        if all_revs:
-            ix = self.index_object.all_revisions_index
-        else:
-            ix = self.index_object.latest_revisions_index
-        with ix.searcher() as searcher:
-            # Note: callers must consume everything we yield, so the for loop
-            # ends and the "with" is left to close the index files.
-            for doc in searcher.documents(**kw):
-                yield doc
-

MoinMoin/storage/backends/router.py

-# Copyright: 2008-2010 MoinMoin:ThomasWaldmann
-# Copyright: 2009 MoinMoin:ChristopherDenter
-# License: GNU GPL v2 (or any later version), see LICENSE.txt for details.
-
-"""
-    MoinMoin - routing backend
-
-    You can use this backend to route requests to different backends
-    depending on the item name. I.e., you can specify mountpoints and
-    map them to different backends. E.g. you could route all your items
-    to an FSBackend and only items below hg/<youritemnamehere> go into
-    a MercurialBackend and similarly tmp/<youritemnamehere> is for
-    temporary items in a MemoryBackend() that are discarded when the
-    process terminates.
-"""
-
-
-import re
-
-from MoinMoin import log
-logging = log.getLogger(__name__)
-
-from MoinMoin.error import ConfigurationError
-from MoinMoin.storage.error import AccessDeniedError
-
-from MoinMoin.storage import Backend as BackendBase
-from MoinMoin.storage import Item as ItemBase
-from MoinMoin.storage import NewRevision as NewRevisionBase
-from MoinMoin.storage import StoredRevision as StoredRevisionBase
-
-from MoinMoin.storage.backends.indexing import IndexingBackendMixin, IndexingItemMixin, IndexingRevisionMixin
-
-from MoinMoin.storage.serialization import SerializableRevisionMixin, SerializableItemMixin, SerializableBackendMixin
-
-
-class BareRouterBackend(BackendBase):
-    """
-    Router Backend - routes requests to different backends depending
-    on the item name.
-
-    For method docstrings, please see the "Backend" base class.
-    """
-    def __init__(self, mapping, *args, **kw):
-        """
-        Initialize router backend.
-
-        The mapping given must satisfy the following criteria:
-            * Order matters.
-            * Mountpoints are just item names, including the special '' (empty)
-              root item name. A trailing '/' of a mountpoint will be ignored.
-            * There *must* be a backend with mountpoint '' (or '/') at the very
-              end of the mapping. That backend is then used as root, which means
-              that all items that don't lie in the namespace of any other
-              backend are stored there.
-
-        :type mapping: list of tuples of mountpoint -> backend mappings
-        :param mapping: [(mountpoint, backend), ...]
-        """
-        super(BareRouterBackend, self).__init__(*args, **kw)
-        self.mapping = [(mountpoint.rstrip('/'), backend) for mountpoint, backend in mapping]
-
-    def close(self):
-        super(BareRouterBackend, self).close()
-        for mountpoint, backend in self.mapping:
-            backend.close()
-        self.mapping = []
-
-    def _get_backend(self, itemname):
-        """
-        For a given fully-qualified itemname (i.e. something like Company/Bosses/Mr_Joe)
-        find the backend it belongs to (given by this instance's mapping), the local
-        itemname inside that backend and the mountpoint of the backend.
-
-        Note: Internally (i.e. in all Router* classes) we always use the normalized
-              item name for consistency reasons.
-
-        :type itemname: str
-        :param itemname: fully-qualified itemname
-        :returns: tuple of (backend, itemname, mountpoint)
-        """
-        if not isinstance(itemname, (str, unicode)):
-            raise TypeError("Item names must have string type, not %s" % (type(itemname)))
-
-        for mountpoint, backend in self.mapping:
-            if itemname == mountpoint or itemname.startswith(mountpoint and mountpoint + '/' or ''):
-                lstrip = mountpoint and len(mountpoint)+1 or 0
-                return backend, itemname[lstrip:], mountpoint
-        raise AssertionError("No backend found for %r. Available backends: %r" % (itemname, self.mapping))
-
-    def get_backend(self, namespace):
-        """
-        Given a namespace, return the backend mounted there.
-
-        :type namespace: basestring
-        :param namespace: The namespace of which we look the backend up.
-        """
-        return self._get_backend(namespace)[0]
-
-    def iter_items_noindex(self):
-        """
-        Iterate over all items.
-
-        Must not use the index as this method is used to *build* the index.
-
-        @see: Backend.iter_items_noindex.__doc__
-        """
-        for mountpoint, backend in self.mapping:
-            for item in backend.iter_items_noindex():
-                yield RouterItem(self, item.name, item, mountpoint)
-
-    # TODO: implement a faster iteritems using the index
-    iteritems = iter_items_noindex
-
-    def has_item(self, itemname):
-        """
-        @see: Backend.has_item.__doc__
-        """
-        # While we could use the inherited, generic implementation
-        # it is generally advised to override this method.
-        # Thus, we pass the call down.
-        logging.debug("has_item: %r" % itemname)
-        backend, itemname, mountpoint = self._get_backend(itemname)
-        return backend.has_item(itemname)
-
-    def get_item(self, itemname):
-        """
-        @see: Backend.get_item.__doc__
-        """
-        logging.debug("get_item: %r" % itemname)
-        backend, itemname, mountpoint = self._get_backend(itemname)
-        return RouterItem(self, itemname, backend.get_item(itemname), mountpoint)
-
-    def create_item(self, itemname):
-        """
-        @see: Backend.create_item.__doc__
-        """
-        logging.debug("create_item: %r" % itemname)
-        backend, itemname, mountpoint = self._get_backend(itemname)
-        return RouterItem(self, itemname, backend.create_item(itemname), mountpoint)
-
-
-class RouterBackend(SerializableBackendMixin, IndexingBackendMixin, BareRouterBackend):
-    pass
-
-
-class BareRouterItem(ItemBase):
-    """
-    Router Item - Wraps 'real' storage items to make them aware of their full name.
-
-    Items stored in the backends managed by the RouterBackend do not know their full
-    name since the backend they belong to is looked up from a list for a given
-    mountpoint and only the itemname itself (without leading mountpoint) is given to
-    the specific backend.
-    This is done so as to allow mounting a given backend at a different mountpoint.
-    The problem with that is, of course, that items do not know their full name if they
-    are retrieved via the specific backends directly. Thus, it is neccessary to wrap the
-    items returned from those specific backends in an instance of this RouterItem class.
-    This makes sure that an item in a specific backend only knows its local name (as it
-    should be; this allows mounting at a different place without renaming all items) but
-    items that the RouterBackend creates or gets know their fully qualified name.
-
-    In order to achieve this, we must mimic the Item interface here. In addition to that,
-    a backend implementor may have decided to provide additional methods on his Item class.
-    We can not know that here, ahead of time. We must redirect any attribute lookup to the
-    encapsulated item, hence, and only intercept calls that are related to the item name.
-    To do this, we store the wrapped item and redirect all calls via this classes __getattr__
-    method. For this to work, RouterItem *must not* inherit from Item, because otherwise
-    the attribute would be looked up on the abstract base class, which certainly is not what
-    we want.
-    Furthermore there's a problem with __getattr__ and new-style classes' special methods
-    which can be looked up here:
-    http://docs.python.org/reference/datamodel.html#special-method-lookup-for-new-style-classes
-    """
-    def __init__(self, backend, item_name, item, mountpoint, *args, **kw):
-        """
-        :type backend: Object adhering to the storage API.
-        :param backend: The backend this item belongs to.
-        :type itemname: basestring.
-        :param itemname: The name of the item (not the FQIN).
-        :type item: Object adhering to the storage item API.
-        :param item: The item we want to wrap.
-        :type mountpoint: basestring.
-        :param mountpoint: The mountpoint where this item is located.
-        """
-        self._get_backend = backend._get_backend
-        self._itemname = item_name
-        self._item = item
-        self._mountpoint = mountpoint
-        super(BareRouterItem, self).__init__(backend, item_name, *args, **kw)
-
-    def __getattr__(self, attr):
-        """
-        Redirect all attribute lookups to the item that is proxied by this instance.
-
-        Note: __getattr__ only deals with stuff that is not found in instance,
-              this class and base classes, so be careful!
-        """
-        return getattr(self._item, attr)
-
-    @property
-    def name(self):
-        """
-        :rtype: str
-        :returns: the item's fully-qualified name
-        """
-        mountpoint = self._mountpoint
-        if mountpoint:
-            mountpoint += '/'
-        return mountpoint + self._itemname
-
-    def __setitem__(self, key, value):
-        """
-        @see: Item.__setitem__.__doc__
-        """
-        return self._item.__setitem__(key, value)
-
-    def __delitem__(self, key):
-        """
-        @see: Item.__delitem__.__doc__
-        """
-        return self._item.__delitem__(key)
-
-    def __getitem__(self, key):
-        """
-        @see: Item.__getitem__.__doc__
-        """
-        return self._item.__getitem__(key)
-
-    def keys(self):
-        return self._item.keys()
-
-    def change_metadata(self):
-        return self._item.change_metadata()
-
-    def publish_metadata(self):
-        return self._item.publish_metadata()
-
-    def rollback(self):
-        return self._item.rollback()
-
-    def commit(self):
-        return self._item.commit()
-
-    def rename(self, newname):
-        """
-        For intra-backend renames, this is the same as the normal Item.rename
-        method.
-        For inter-backend renames, this *moves* the complete item over to the
-        new backend, possibly with a new item name.
-        In order to avoid content duplication, the old item is destroyed after
-        having been copied (in inter-backend scenarios only, of course).
-
-        @see: Item.rename.__doc__
-        """
-        old_name = self._item.name
-        backend, itemname, mountpoint = self._get_backend(newname)
-        if mountpoint != self._mountpoint:
-            # Mountpoint changed! That means we have to copy the item over.
-            converts, skips, fails = backend.copy_item(self._item, verbose=False, name=itemname)
-            assert len(converts) == 1
-
-            new_item = backend.get_item(itemname)
-            old_item = self._item
-            self._item = new_item
-            self._mountpoint = mountpoint
-            self._itemname = itemname
-            # We destroy the old item in order not to duplicate data.
-            # It may be the case that the item we want to destroy is ACL protected. In that case,
-            # the destroy() below doesn't irreversibly kill the item because at this point it is already
-            # guaranteed that it lives on at another place and we do not require 'destroy' hence.
-            try:
-                # Perhaps we don't deal with acl protected items anyway.
-                old_item.destroy()
-            except AccessDeniedError:
-                # OK, we're indeed routing to an ACL protected backend. Use unprotected item.
-                old_item._item.destroy()
-
-        else:
-            # Mountpoint didn't change
-            self._item.rename(itemname)
-            self._itemname = itemname
-
-    def list_revisions(self):
-        return self._item.list_revisions()
-
-    def create_revision(self, revno):
-        """
-        In order to make item name lookups via revision.item.name work, we need
-        to wrap the revision here.
-
-        @see: Item.create_revision.__doc__
-        """
-        rev = self._item.create_revision(revno)
-        return NewRouterRevision(self, revno, rev)
-
-    def get_revision(self, revno):
-        """
-        In order to make item name lookups via revision.item.name work, we need
-        to wrap the revision here.
-
-        @see: Item.get_revision.__doc__
-        """
-        rev = self._item.get_revision(revno)
-        return StoredRouterRevision(self, revno, rev)
-
-    def destroy(self):
-        """
-        ATTENTION!
-        This method performs an irreversible operation and deletes potentially important
-        data. Use with great care.
-
-        @see: Item.destroy.__doc__
-        """
-        return self._item.destroy()
-
-
-class RouterItem(SerializableItemMixin, IndexingItemMixin, BareRouterItem):
-    pass
-
-
-class BareNewRouterRevision(NewRevisionBase):
-    """
-    """
-    def __init__(self, item, revno, revision, *args, **kw):
-        self._item = item
-        self._revision = revision
-        super(BareNewRouterRevision, self).__init__(item, revno, *args, **kw)
-
-    def __getattr__(self, attr):
-        """
-        Redirect all attribute lookups to the revision that is proxied by this instance.
-
-        Note: __getattr__ only deals with stuff that is not found in instance,
-              this class and base classes, so be careful!
-        """
-        return getattr(self._revision, attr)
-
-    @property
-    def item(self):
-        """
-        Here we have to return the RouterItem, which in turn wraps the real item
-        and provides it with its full name that we need for the rev.item.name lookup.
-
-        @see: Revision.item.__doc__
-        """
-        assert isinstance(self._item, RouterItem)
-        return self._item
-
-    @property
-    def revno(self):
-        return self._revision.revno
-
-    @property
-    def timestamp(self):
-        return self._revision.timestamp
-
-    def __setitem__(self, key, value):
-        """
-        We only need to redirect this manually here because python doesn't do that
-        in combination with __getattr__. See RouterBackend.__doc__ for an explanation.
-
-        As this class wraps generic Revisions, this may very well result in an exception
-        being raised if the wrapped revision is a StoredRevision.
-        """
-        return self._revision.__setitem__(key, value)
-
-    def __delitem__(self, key):
-        """
-        @see: RouterRevision.__setitem__.__doc__
-        """
-        return self._revision.__delitem__(key)
-
-    def __getitem__(self, key):
-        """
-        @see: RouterRevision.__setitem__.__doc__
-        """
-        return self._revision.__getitem__(key)
-
-    def keys(self):
-        return self._revision.keys()
-
-    def read(self, chunksize=-1):
-        return self._revision.read(chunksize)
-
-    def seek(self, position, mode=0):
-        return self._revision.seek(position, mode)
-
-    def tell(self):
-        return self._revision.tell()
-
-    def write(self, data):
-        self._revision.write(data)
-
-    def destroy(self):
-        return self._revision.destroy()
-
-
-class NewRouterRevision(SerializableRevisionMixin, IndexingRevisionMixin, BareNewRouterRevision):
-    pass
-
-class BareStoredRouterRevision(StoredRevisionBase):
-    """
-    """
-    def __init__(self, item, revno, revision, *args, **kw):
-        self._item = item
-        self._revision = revision
-        super(BareStoredRouterRevision, self).__init__(item, revno, *args, **kw)
-
-    def __getattr__(self, attr):
-        """
-        Redirect all attribute lookups to the revision that is proxied by this instance.
-
-        Note: __getattr__ only deals with stuff that is not found in instance,
-              this class and base classes, so be careful!
-        """
-        return getattr(self._revision, attr)
-
-    @property
-    def item(self):
-        """
-        Here we have to return the RouterItem, which in turn wraps the real item
-        and provides it with its full name that we need for the rev.item.name lookup.
-
-        @see: Revision.item.__doc__
-        """
-        assert isinstance(self._item, RouterItem)
-        return self._item
-
-    @property
-    def revno(self):
-        return self._revision.revno
-
-    @property
-    def timestamp(self):
-        return self._revision.timestamp
-
-    def __getitem__(self, key):
-        return self._revision.__getitem__(key)
-
-    def keys(self):
-        return self._revision.keys()
-
-    def read(self, chunksize=-1):
-        return self._revision.read(chunksize)
-
-    def seek(self, position, mode=0):
-        return self._revision.seek(position, mode)
-
-    def tell(self):
-        return self._revision.tell()
-