]> git.ipfire.org Git - ipfire.org.git/commitdiff
wiki: Add ACLs
authorMichael Tremer <michael.tremer@ipfire.org>
Fri, 23 Nov 2018 23:11:24 +0000 (23:11 +0000)
committerMichael Tremer <michael.tremer@ipfire.org>
Fri, 23 Nov 2018 23:17:14 +0000 (23:17 +0000)
Signed-off-by: Michael Tremer <michael.tremer@ipfire.org>
src/backend/wiki.py
src/web/wiki.py

index 33ed40405d4004bfae53435bbadde72dd7353aae..7962ea0b9acb89cc441065fee2641df35a926606 100644 (file)
@@ -42,10 +42,20 @@ class Wiki(misc.Object):
                        return self._get_page("SELECT * FROM wiki WHERE page = %s \
                                ORDER BY timestamp DESC LIMIT 1", page)
 
-       def get_recent_changes(self, limit=None):
-               return self._get_pages("SELECT * FROM wiki \
+       def get_recent_changes(self, account, limit=None):
+               pages = self._get_pages("SELECT * FROM wiki \
                        WHERE timestamp >= NOW() - INTERVAL '4 weeks' \
-                       ORDER BY timestamp DESC LIMIT %s", limit)
+                       ORDER BY timestamp DESC")
+
+               for page in pages:
+                       if not page.check_acl(account):
+                               continue
+
+                       yield page
+
+                       limit -= 1
+                       if not limit:
+                               break
 
        def create_page(self, page, author, content, changes=None, address=None):
                page = Page.sanitise_page_name(page)
@@ -71,16 +81,26 @@ class Wiki(misc.Object):
 
                return ret
 
-       def search(self, query, limit=None):
+       def search(self, query, account=None, limit=None):
                query = util.parse_search_query(query)
 
                res = self._get_pages("SELECT wiki.* FROM wiki_search_index search_index \
                        LEFT JOIN wiki ON search_index.wiki_id = wiki.id \
                        WHERE search_index.document @@ to_tsquery('english', %s) \
-                               ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC \
-                       LIMIT %s", query, query, limit)
+                               ORDER BY ts_rank(search_index.document, to_tsquery('english', %s)) DESC",
+                       query, query)
 
-               return list(res)
+               for page in res:
+                       # Skip any pages the user doesn't have permission for
+                       if not page.check_acl(account):
+                               continue
+
+                       # Return any other pages
+                       yield page
+
+                       limit -= 1
+                       if not limit:
+                               break
 
        def refresh(self):
                """
@@ -88,6 +108,28 @@ class Wiki(misc.Object):
                """
                self.db.execute("REFRESH MATERIALIZED VIEW wiki_search_index")
 
+       # ACL
+
+       def check_acl(self, page, account):
+               res = self.db.query("SELECT * FROM wiki_acls \
+                       WHERE %s ILIKE (path || '%%') ORDER BY LENGTH(path) DESC LIMIT 1", page)
+
+               for row in res:
+                       # Access not permitted when user is not logged in
+                       if not account:
+                               return False
+
+                       # If user is in a matching group, we grant permission
+                       for group in row.groups:
+                               if group in account.groups:
+                                       return True
+
+                       # Otherwise access is not permitted
+                       return False
+
+               # If no ACLs are found, we permit access
+               return True
+
        # Files
 
        def _get_files(self, query, *args):
@@ -285,6 +327,11 @@ class Page(misc.Object):
        def changes(self):
                return self.data.changes
 
+       # ACL
+
+       def check_acl(self, account):
+               return self.backend.wiki.check_acl(self.page, account)
+
        # Sidebar
 
        @lazy_property
index bc59b25a431f7849a4a86545b8e6dfd19599c372..4f92be24f46ceda84d6d72e0427f3c2e6f5baf51 100644 (file)
@@ -11,6 +11,10 @@ class ActionUploadHandler(auth.CacheMixin, base.BaseHandler):
        def post(self):
                path = self.get_argument("path")
 
+               # Check permissions
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+
                try:
                        filename, data, mimetype = self.get_file("file")
 
@@ -30,6 +34,10 @@ class ActionUploadHandler(auth.CacheMixin, base.BaseHandler):
 class FilesHandler(auth.CacheMixin, base.BaseHandler):
        @tornado.web.authenticated
        def get(self, path):
+               # Check permissions
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+
                files = self.backend.wiki.get_files(path)
 
                self.render("wiki/files/index.html", path=path, files=files)
@@ -41,6 +49,11 @@ class FileHandler(base.BaseHandler):
                return self.get_argument("action", None)
 
        def get(self, path):
+               # Check permissions
+               if not self.backend.wiki.check_acl(path, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (path, self.current_user))
+
+               # Fetch the file
                file = self.backend.wiki.get_file_by_path(path)
                if not file:
                        raise tornado.web.HTTPError(404, "Could not find %s" % path)
@@ -85,6 +98,10 @@ class PageHandler(auth.CacheMixin, base.BaseHandler):
 
        @tornado.web.removeslash
        def get(self, page):
+               # Check permissions
+               if not self.backend.wiki.check_acl(page, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (page, self.current_user))
+
                # Check if we are asked to render a certain revision
                revision = self.get_argument("revision", None)
 
@@ -121,6 +138,10 @@ class PageHandler(auth.CacheMixin, base.BaseHandler):
 
        @tornado.web.authenticated
        def post(self, page):
+               # Check permissions
+               if not self.backend.wiki.check_acl(page, self.current_user):
+                       raise tornado.web.HTTPError(403, "Access to %s not allowed for %s" % (page, self.current_user))
+
                content = self.get_argument("content", None)
                changes = self.get_argument("changes")
 
@@ -145,14 +166,14 @@ class SearchHandler(auth.CacheMixin, base.BaseHandler):
        def get(self):
                q = self.get_argument("q")
 
-               pages = self.backend.wiki.search(q, limit=50)
+               pages = self.backend.wiki.search(q, account=self.current_user, limit=50)
 
-               self.render("wiki/search-results.html", q=q, pages=pages)
+               self.render("wiki/search-results.html", q=q, pages=list(pages))
 
 
 class RecentChangesHandler(auth.CacheMixin, base.BaseHandler):
        def get(self):
-               recent_changes = self.backend.wiki.get_recent_changes(limit=50)
+               recent_changes = self.backend.wiki.get_recent_changes(self.current_user, limit=50)
 
                self.render("wiki/recent-changes.html", recent_changes=recent_changes)