changeset 2219:16ce58503062

Updated name validator and added itemid validator for composite name.
author Ashutosh Singla <ashu1461@gmail.com>
date Sun, 14 Jul 2013 16:44:31 +0530
parents 6a798f444129
children 81652de0216c
files MoinMoin/_tests/test_forms.py MoinMoin/forms.py MoinMoin/items/__init__.py MoinMoin/storage/middleware/_tests/test_validation.py MoinMoin/storage/middleware/indexing.py MoinMoin/storage/middleware/protecting.py MoinMoin/storage/middleware/validation.py
diffstat 7 files changed, 97 insertions(+), 26 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/_tests/test_forms.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/_tests/test_forms.py	Sun Jul 14 16:44:31 2013 +0530
@@ -6,9 +6,17 @@
 """
 
 import datetime
+import json
 from calendar import timegm
 
-from MoinMoin.forms import DateTimeUNIX
+from flask import current_app as app
+from flask import g as flaskg
+
+from MoinMoin.forms import DateTimeUNIX, JSON
+from MoinMoin.util.interwiki import CompositeName
+from MoinMoin.items import Item
+from MoinMoin._tests import become_trusted
+from MoinMoin.constants.keys import ITEMID, NAME, CONTENTTYPE, NAMESPACE, FQNAME
 
 
 def test_datetimeunix():
@@ -42,3 +50,27 @@
     d = DateTimeUNIX(None)
     assert d.value is None
     assert d.u == u''
+
+
+def test_validjson():
+    app.cfg.namespace_mapping = [(u'', 'default_backend'), (u'ns1/', 'default_backend'), (u'ns1/ns2/', 'other_backend')]
+    item = Item.create(u'ns1/ns2/existingname')
+    meta = {NAMESPACE: u'ns1/ns2', CONTENTTYPE: u'text/plain;charset=utf-8'}
+    become_trusted()
+    item._save(meta, data='This is a valid Item.')
+
+    valid_itemid = 'a1924e3d0a34497eab18563299d32178'
+    # ('names', 'namespace', 'field', 'value', 'result')
+    tests = [([u'somename', u'@revid'], '', '', 'somename', False),
+             ([u'bar', u'ns1'], '', '', 'bar', False),
+             ([u'foo', u'foo', u'bar'], '', '', 'foo', False),
+             ([u'ns1ns2ns3', u'ns1/subitem'], '', '', 'valid', False),
+             ([u'foobar', u'validname'], '', ITEMID, valid_itemid + '8080', False),
+             ([u'barfoo', u'validname'], '', ITEMID, valid_itemid.replace('a', 'y'), False),
+             ([], '', 'itemid', valid_itemid, True),
+             ([u'existingname'], 'ns1/ns2', '', 'existingname', False),
+             ]
+    for name, namespace, field, value, result in tests:
+        x = JSON(json.dumps({NAME: name}))
+        state = {FQNAME: CompositeName(namespace, field, value), ITEMID: None}
+        assert x.validate(state) == result
--- a/MoinMoin/forms.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/forms.py	Sun Jul 14 16:44:31 2013 +0530
@@ -23,11 +23,13 @@
 from whoosh.query import Term, Or, Not, And
 
 from flask import g as flaskg
+from flask import current_app as app
 
 from MoinMoin.constants.forms import *
-from MoinMoin.constants.keys import ITEMID, NAME, LATEST_REVS
+from MoinMoin.constants.keys import ITEMID, NAME, LATEST_REVS, NAMESPACE, FQNAME
 from MoinMoin.i18n import _, L_, N_
 from MoinMoin.util.forms import FileStorage
+from MoinMoin.storage.middleware.validation import uuid_validator
 
 
 class Enum(BaseEnum):
@@ -69,40 +71,69 @@
 RequiredMultilineText = MultilineText.validated_by(Present())
 
 
+class NameNotValidError(ValueError):
+    """
+    The name is not valid.
+    """
+
+
+def validate_name(meta, fqname, itemid):
+    """
+    Check whether the names are valid.
+    Will just return, if they are valid, will raise a NameNotValidError if not.
+    """
+    names = meta.get(NAME)
+    current_namespace = meta.get(NAMESPACE, fqname.namespace)
+    if names is None:
+        return
+    if len(names) != len(set(names)):
+        raise NameNotValidError(L_("The names in the name list must be unique."))
+    # Item names must not start with '@' or '+', '@something' denotes a field where as '+something' denotes a view.
+    invalid_names = [name for name in names if name.startswith(('@', '+'))]
+    if invalid_names:
+        raise NameNotValidError(L_("Item names (%(invalid_names)s) must not start with '@' or '+'", invalid_names=", ".join(invalid_names)))
+    # Item names must not match with existing namespaces.
+    namespaces = [namespace.rstrip('/') for namespace, _ in app.cfg.namespace_mapping]
+    invalid_names = [name for name in names if name.split('/', 1)[0] in namespaces]
+    if invalid_names:
+        raise NameNotValidError(L_("Item names (%(invalid_names)s) must not match with existing namespaces.", invalid_names=", ".join(invalid_names)))
+    query = And([Or([Term(NAME, name) for name in names]), Term(NAMESPACE, current_namespace)])
+    # There should be not item existing with the same name.
+    if itemid is not None:
+        query = And([query, Not(Term(ITEMID, itemid))])  # search for items except the current item.
+    with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher:
+        results = searcher.search(query)
+        duplicate_names = {name for result in results for name in result[NAME] if name in names}
+        if duplicate_names:
+            raise NameNotValidError(L_("Item(s) named %(duplicate_names)s already exist.", duplicate_names=", ".join(duplicate_names)))
+
+
 class ValidJSON(Validator):
     """Validator for JSON
     """
     invalid_json_msg = L_('Invalid JSON.')
     invalid_name_msg = ""
+    invalid_itemid_msg = L_('Itemid not a proper UUID')
 
-    def validname(self, meta, name, itemid):
-        names = meta.get(NAME)
-        if names is None:
-            self.invalid_name_msg = L_("No name field in the JSON meta.")
-            return False
-        if len(names) != len(set(names)):
-            self.invalid_name_msg = L_("The names in the JSON name list must be unique.")
+    def validitemid(self, itemid):
+        if not itemid:
+            self.invalid_itemid_msg = L_("No ITEMID field")
             return False
-        query = Or([Term(NAME, x) for x in names])
-        if itemid is not None:
-            query = And([query, Not(Term(ITEMID, itemid))])
-        duplicate_names = set()
-        with flaskg.storage.indexer.ix[LATEST_REVS].searcher() as searcher:
-            results = searcher.search(query)
-            for result in results:
-                duplicate_names |= set([x for x in result[NAME] if x in names])
-        if duplicate_names:
-            self.invalid_name_msg = L_("Item(s) named %(duplicate_names)s already exist.", duplicate_names=", ".join(duplicate_names))
-            return False
-        return True
+        return uuid_validator(String(itemid), None)
 
     def validate(self, element, state):
         try:
             meta = json.loads(element.value)
         except:  # catch ANY exception that happens due to unserializing
             return self.note_error(element, state, 'invalid_json_msg')
-        if not self.validname(meta, state[NAME], state[ITEMID]):
+        try:
+            validate_name(meta, state[FQNAME], state[ITEMID])
+        except NameNotValidError as e:
+            self.invalid_name_msg = _(e)
             return self.note_error(element, state, 'invalid_name_msg')
+        if state[FQNAME].field == ITEMID:
+            if not self.validitemid(meta.get(ITEMID)):
+                return self.note_error(element, state, 'invalid_itemid_msg')
         return True
 
 JSON = OptionalMultilineText.with_properties(lang='en', dir='ltr').validated_by(ValidJSON())
--- a/MoinMoin/items/__init__.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/items/__init__.py	Sun Jul 14 16:44:31 2013 +0530
@@ -537,6 +537,7 @@
                                              contenttype_current=contenttype_current,
                                              contenttype_guessed=contenttype_guessed,
                                              return_rev=True,
+                                             fqname=self.fqname
                                              )
         item_modified.send(app._get_current_object(), item_name=name)
         return newrev.revid, newrev.meta[SIZE]
@@ -747,7 +748,7 @@
                     # break them
                     return "OK"
             form = self.ModifyForm.from_request(request)
-            state = dict(name=self.name, itemid=self.meta.get(ITEMID))
+            state = dict(fqname=self.fqname, itemid=self.meta.get(ITEMID))
             if form.validate(state):
                 meta, data, contenttype_guessed, comment = form._dump(self)
                 contenttype_qs = request.values.get('contenttype')
--- a/MoinMoin/storage/middleware/_tests/test_validation.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/storage/middleware/_tests/test_validation.py	Sun Jul 14 16:44:31 2013 +0530
@@ -15,6 +15,8 @@
 
 from MoinMoin.util.crypto import make_uuid
 
+from MoinMoin.util.interwiki import CompositeName
+
 
 class TestValidation(object):
     def test_content(self):
@@ -49,6 +51,7 @@
                  'acl_parent': u"All:read",
                  'contenttype_current': u'text/x.moin.wiki;charset=utf-8',
                  'contenttype_guessed': u'text/plain;charset=utf-8',
+                 keys.FQNAME: CompositeName(u'', u'', u'somename'),
                 }
 
         m = ContentMetaSchema(meta)
@@ -76,6 +79,7 @@
                  keys.ADDRESS: u'127.0.0.1',
                  keys.WIKINAME: u'ThisWiki',
                  keys.NAMESPACE: u'',
+                 keys.FQNAME: CompositeName(u'', u'', u'somename')
                 }
 
         m = UserMetaSchema(meta)
--- a/MoinMoin/storage/middleware/indexing.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/storage/middleware/indexing.py	Sun Jul 14 16:44:31 2013 +0530
@@ -1010,6 +1010,7 @@
                        contenttype_guessed=None,
                        acl_parent=None,
                        return_rev=False,
+                       fqname=None
                        ):
         """
         Store a revision into the backend, write metadata and data to it.
@@ -1049,6 +1050,7 @@
                  'contenttype_current': contenttype_current,
                  'contenttype_guessed': contenttype_guessed,
                  'acl_parent': acl_parent,
+                 FQNAME: fqname,
                 }
         ct = meta.get(CONTENTTYPE)
         if ct == CONTENTTYPE_USER:
--- a/MoinMoin/storage/middleware/protecting.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/storage/middleware/protecting.py	Sun Jul 14 16:44:31 2013 +0530
@@ -291,13 +291,13 @@
     def get_revision(self, revid):
         return self[revid]
 
-    def store_revision(self, meta, data, overwrite=False, return_rev=False, **kw):
+    def store_revision(self, meta, data, overwrite=False, return_rev=False, fqname=None, **kw):
         self.require(WRITE)
         if not self:
             self.require(CREATE)
         if overwrite:
             self.require(DESTROY)
-        rev = self.item.store_revision(meta, data, overwrite=overwrite, return_rev=return_rev, **kw)
+        rev = self.item.store_revision(meta, data, overwrite=overwrite, return_rev=return_rev, fqname=fqname, **kw)
         self.protector._clear_acl_cache()
         if return_rev:
             return ProtectedRevision(self.protector, rev, p_item=self)
--- a/MoinMoin/storage/middleware/validation.py	Sun Jun 30 02:53:51 2013 +0530
+++ b/MoinMoin/storage/middleware/validation.py	Sun Jul 14 16:44:31 2013 +0530
@@ -61,7 +61,8 @@
     an itemid is a uuid that identifies an item
     """
     if not state['trusted'] or element.raw is Unset:
-        itemid = state.get(keys.ITEMID)
+        fqname = state[keys.FQNAME]
+        itemid = fqname.value if fqname and fqname.field == keys.ITEMID else state.get(keys.ITEMID)
         if itemid is None:
             # this is first revision of an item
             itemid = make_uuid()