changeset 1777:1db99f8f5144

add 'pubread' capability/permission (needs index rebuild!) read means to be able to read revision data, unconditionally pubread means to be able to read revision data when published moved PTIME from blog-specific to common fields, because of this an index rebuild is required.
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Fri, 07 Sep 2012 23:34:57 +0200
parents 9890a2d41efe
children d56cd193cca0
files MoinMoin/constants/rights.py MoinMoin/search/_tests/test_analyzers.py MoinMoin/security/__init__.py MoinMoin/storage/middleware/indexing.py MoinMoin/storage/middleware/protecting.py
diffstat 5 files changed, 72 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/constants/rights.py	Fri Sep 07 21:51:04 2012 +0200
+++ b/MoinMoin/constants/rights.py	Fri Sep 07 23:34:57 2012 +0200
@@ -21,10 +21,13 @@
 # admin means to be able to change, add, remove ACLs (change meta[ACL])
 ADMIN = 'admin'
 
-# read means to be able to read revision data
+# read means to be able to read revision data, unconditionally
 # TODO: define revision meta read behaviour
 READ = 'read'
 
+# pubread means to be able to read revision data when published
+PUBREAD = 'pubread'
+
 # write means to be able to change meta/data by creating a new revision,
 # so the previous data is still there, unchanged.
 WRITE = 'write'
@@ -37,4 +40,4 @@
 DESTROY = 'destroy'
 
 # rights that control access to operations on contents
-ACL_RIGHTS_CONTENTS = [READ, WRITE, CREATE, ADMIN, DESTROY, ]
+ACL_RIGHTS_CONTENTS = [READ, PUBREAD, WRITE, CREATE, ADMIN, DESTROY, ]
--- a/MoinMoin/search/_tests/test_analyzers.py	Fri Sep 07 21:51:04 2012 +0200
+++ b/MoinMoin/search/_tests/test_analyzers.py	Fri Sep 07 23:34:57 2012 +0200
@@ -32,6 +32,7 @@
         (u'Admin3:read,write,admin',
             [
              u'Admin3:+read',
+             u'Admin3:-pubread',
              u'Admin3:+write',
              u'Admin3:-create',
              u'Admin3:+admin',
@@ -41,20 +42,23 @@
         (u'Admin1,Admin2:read,write,admin',
             [
              u'Admin1:+read',
+             u'Admin1:-pubread',
              u'Admin1:+write',
              u'Admin1:-create',
              u'Admin1:+admin',
              u'Admin1:-destroy',
              u'Admin2:+read',
+             u'Admin2:-pubread',
              u'Admin2:+write',
              u'Admin2:-create',
              u'Admin2:+admin',
              u'Admin2:-destroy',
             ]
         ),
-        (u'JoeDoe:read,write',
+        (u'JoeDoe:pubread,write',
             [
-             u'JoeDoe:+read',
+             u'JoeDoe:-read',
+             u'JoeDoe:+pubread',
              u'JoeDoe:+write',
              u'JoeDoe:-create',
              u'JoeDoe:-admin',
@@ -64,11 +68,13 @@
         (u'name with spaces,another one:read,write',
             [
              u'name with spaces:+read',
+             u'name with spaces:-pubread',
              u'name with spaces:+write',
              u'name with spaces:-create',
              u'name with spaces:-admin',
              u'name with spaces:-destroy',
              u'another one:+read',
+             u'another one:-pubread',
              u'another one:+write',
              u'another one:-create',
              u'another one:-admin',
@@ -78,11 +84,13 @@
         (u'CamelCase,extended name:read,write',
             [
              u'CamelCase:+read',
+             u'CamelCase:-pubread',
              u'CamelCase:+write',
              u'CamelCase:-create',
              u'CamelCase:-admin',
              u'CamelCase:-destroy',
              u'extended name:+read',
+             u'extended name:-pubread',
              u'extended name:+write',
              u'extended name:-create',
              u'extended name:-admin',
@@ -92,6 +100,7 @@
         (u'BadGuy:',
             [
              u'BadGuy:-read',
+             u'BadGuy:-pubread',
              u'BadGuy:-write',
              u'BadGuy:-create',
              u'BadGuy:-admin',
@@ -101,6 +110,7 @@
         (u'All:read',
             [
              u'All:+read',
+             u'All:-pubread',
              u'All:-write',
              u'All:-create',
              u'All:-admin',
--- a/MoinMoin/security/__init__.py	Fri Sep 07 21:51:04 2012 +0200
+++ b/MoinMoin/security/__init__.py	Fri Sep 07 23:34:57 2012 +0200
@@ -16,6 +16,7 @@
 from flask import g as flaskg
 from flask import abort
 
+from MoinMoin.constants import rights
 from MoinMoin import user
 from MoinMoin.i18n import _, L_, N_
 
@@ -65,6 +66,16 @@
     def __init__(self, user):
         self.name = user.name
 
+    def read(self, itemname):
+        """read permission is special as we have 2 kinds of read capabilities:
+
+           * READ - gives permission to read, unconditionally
+           * PUBREAD - gives permission to read, when published
+        """
+        return (flaskg.storage.may(itemname, rights.READ, username=self.name)
+                or
+                flaskg.storage.may(itemname, rights.PUBREAD, username=self.name))
+
     def __getattr__(self, attr):
         """ Shortcut to handle all known ACL rights.
 
--- a/MoinMoin/storage/middleware/indexing.py	Fri Sep 07 21:51:04 2012 +0200
+++ b/MoinMoin/storage/middleware/indexing.py	Fri Sep 07 23:34:57 2012 +0200
@@ -227,6 +227,8 @@
             PARENTID: ID(stored=True),
             # MTIME from revision metadata (converted to UTC datetime)
             MTIME: DATETIME(stored=True),
+            # publish time from metadata (converted to UTC datetime)
+            PTIME: DATETIME(stored=True),
             # ITEMTYPE from metadata, always matched exactly hence ID
             ITEMTYPE: ID(stored=True),
             # tokenized CONTENTTYPE from metadata
@@ -288,8 +290,6 @@
         latest_revs_fields.update(**ticket_fields)
 
         blog_entry_fields = {
-            # publish time from metadata (converted to UTC datetime)
-            PTIME: DATETIME(stored=True)
         }
         latest_revs_fields.update(**blog_entry_fields)
 
@@ -760,6 +760,18 @@
         return self._current.get(ACL)
 
     @property
+    def ptime(self):
+        dt = self._current.get(PTIME)
+        if dt is not None:
+            return utctimestamp(dt)
+
+    @property
+    def mtime(self):
+        dt = self._current.get(MTIME)
+        if dt is not None:
+            return utctimestamp(dt)
+
+    @property
     def name(self):
         return self._current.get(NAME, 'DoesNotExist')
 
--- a/MoinMoin/storage/middleware/protecting.py	Fri Sep 07 21:51:04 2012 +0200
+++ b/MoinMoin/storage/middleware/protecting.py	Fri Sep 07 23:34:57 2012 +0200
@@ -14,11 +14,13 @@
 
 from __future__ import absolute_import, division
 
+import time
+
 from MoinMoin import log
 logging = log.getLogger(__name__)
 
-from MoinMoin.config import ACL, CREATE, READ, WRITE, DESTROY, ADMIN, \
-                            ACL_RIGHTS_CONTENTS, \
+from MoinMoin.config import ACL, CREATE, READ, PUBREAD, WRITE, DESTROY, ADMIN, \
+                            PTIME, ACL_RIGHTS_CONTENTS, \
                             ALL_REVS, LATEST_REVS
 from MoinMoin.security import AccessControlList
 
@@ -29,6 +31,19 @@
     """
 
 
+def pchecker(right, allowed, item):
+    """some permissions need additional checking"""
+    if allowed and right == PUBREAD:
+        # PUBREAD permission is only granted after publication time (ptime)
+        # if PTIME is not defined, we use MTIME (which is usually in the past)
+        # if MTIME is not defined, we use now.
+        # TODO: implement sth like PSTARTTIME <= now <= PENDTIME ?
+        now = time.time()
+        ptime = item.ptime or item.mtime or now
+        allowed = now >= ptime
+    return allowed
+
+
 class ProtectingMiddleware(object):
     def __init__(self, indexer, user, acl_mapping):
         """
@@ -55,26 +70,26 @@
     def search(self, q, idx_name=LATEST_REVS, **kw):
         for rev in self.indexer.search(q, idx_name, **kw):
             rev = ProtectedRevision(self, rev)
-            if rev.allows(READ):
+            if rev.allows(READ) or rev.allows(PUBREAD):
                 yield rev
 
     def search_page(self, q, idx_name=LATEST_REVS, pagenum=1, pagelen=10, **kw):
         for rev in self.indexer.search_page(q, idx_name, pagenum, pagelen, **kw):
             rev = ProtectedRevision(self, rev)
-            if rev.allows(READ):
+            if rev.allows(READ) or rev.allows(PUBREAD):
                 yield rev
 
     def documents(self, idx_name=LATEST_REVS, **kw):
         for rev in self.indexer.documents(idx_name, **kw):
             rev = ProtectedRevision(self, rev)
-            if rev.allows(READ):
+            if rev.allows(READ) or rev.allows(PUBREAD):
                 yield rev
 
     def document(self, idx_name=LATEST_REVS, **kw):
         rev = self.indexer.document(idx_name, **kw)
         if rev:
             rev = ProtectedRevision(self, rev)
-            if rev.allows(READ):
+            if rev.allows(READ) or rev.allows(PUBREAD):
                 return rev
 
     def has_item(self, name):
@@ -135,7 +150,7 @@
             acl = AccessControlList([acl, ], acls['default'], valid=self.protector.valid_rights)
             allowed = acl.may(user_name, right)
             if allowed is not None:
-                return allowed
+                return pchecker(right, allowed, self.item)
         else:
             if acls['hierarchic']:
                 # check parent(s), recursively
@@ -145,12 +160,12 @@
                     parent_item = self.protector[parent]
                     allowed = parent_item._allows(right, user_name)
                     if allowed is not None:
-                        return allowed
+                        return pchecker(right, allowed, self.item)
 
             acl = AccessControlList([acls['default'], ], valid=self.protector.valid_rights)
             allowed = acl.may(user_name, right)
             if allowed is not None:
-                return allowed
+                return pchecker(right, allowed, self.item)
 
     def allows(self, right, user_name=None):
         """ Check if username may have <right> access on item <itemname>.
@@ -184,16 +199,16 @@
         before = AccessControlList([acls['before'], ], valid=self.protector.valid_rights)
         allowed = before.may(user_name, right)
         if allowed is not None:
-            return allowed
+            return pchecker(right, allowed, self.item)
 
         allowed = self._allows(right, user_name)
         if allowed is not None:
-            return allowed
+            return pchecker(right, allowed, self.item)
 
         after = AccessControlList([acls['after'], ], valid=self.protector.valid_rights)
         allowed = after.may(user_name, right)
         if allowed is not None:
-            return allowed
+            return pchecker(right, allowed, self.item)
 
         return False
 
@@ -211,7 +226,7 @@
                 yield ProtectedRevision(self.protector, rev, p_item=self)
 
     def __getitem__(self, revid):
-        self.require(READ)
+        self.require(READ, PUBREAD)
         rev = self.item[revid]
         return ProtectedRevision(self.protector, rev, p_item=self)
 
@@ -268,12 +283,12 @@
 
     @property
     def meta(self):
-        self.require(READ)
+        self.require(READ, PUBREAD)
         return self.rev.meta
 
     @property
     def data(self):
-        self.require(READ)
+        self.require(READ, PUBREAD)
         return self.rev.data
 
     def close(self):