Commits

Thomas Waldmann committed 1db99f8

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.

Comments (0)

Files changed (5)

MoinMoin/constants/rights.py

 # 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'
 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, ]

MoinMoin/search/_tests/test_analyzers.py

         (u'Admin3:read,write,admin',
             [
              u'Admin3:+read',
+             u'Admin3:-pubread',
              u'Admin3:+write',
              u'Admin3:-create',
              u'Admin3:+admin',
         (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',
         (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',
         (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',
         (u'BadGuy:',
             [
              u'BadGuy:-read',
+             u'BadGuy:-pubread',
              u'BadGuy:-write',
              u'BadGuy:-create',
              u'BadGuy:-admin',
         (u'All:read',
             [
              u'All:+read',
+             u'All:-pubread',
              u'All:-write',
              u'All:-create',
              u'All:-admin',

MoinMoin/security/__init__.py

 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_
 
     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.
 

MoinMoin/storage/middleware/indexing.py

             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
         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)
 
         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')
 

MoinMoin/storage/middleware/protecting.py

 
 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
 
     """
 
 
+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):
         """
     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):
             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
                     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>.
         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
 
                 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)
 
 
     @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):