This is an automated email from the ASF dual-hosted git repository. gcruz pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/allura.git
commit 431109b7ccb9b2f772cd05f0103052471642d2b6 Author: Dave Brondsema <dbronds...@slashdotmedia.com> AuthorDate: Tue Apr 2 17:44:28 2024 -0400 [#8555] some specific checks for blocked users, when creating new forum threads --- Allura/allura/lib/security.py | 32 +++++++++++++++------- Allura/allura/model/discuss.py | 6 +++- .../forgediscussion/controllers/root.py | 10 +++++-- .../tests/functional/test_forum_admin.py | 17 ++++++++++-- 4 files changed, 49 insertions(+), 16 deletions(-) diff --git a/Allura/allura/lib/security.py b/Allura/allura/lib/security.py index cc874822d..3c16d05be 100644 --- a/Allura/allura/lib/security.py +++ b/Allura/allura/lib/security.py @@ -19,12 +19,12 @@ This module provides the security predicates used in decorating various models. """ -import six -import sys +from __future__ import annotations import logging from collections import defaultdict import hashlib import requests +import typing from tg import tmpl_context as c from tg import request @@ -35,6 +35,9 @@ import tg from allura.lib.utils import TruthyCallable +if typing.TYPE_CHECKING: + from allura.model import M + log = logging.getLogger(__name__) @@ -277,7 +280,21 @@ class RoleCache: return set(self.reaching_ids) -def has_access(obj, permission, user=None, project=None): +def is_denied(obj, permission: str, user: M.User, project: M.Project) -> bool: + from allura import model as M + + if user != M.User.anonymous(): + user_roles = Credentials.get().user_roles(user_id=user._id, + project_id=project.root_project._id) + for r in user_roles: + deny_user = M.ACE.deny(r['_id'], permission) + if M.ACL.contains(deny_user, obj.acl): + return True + + return False + + +def has_access(obj, permission: str, user: M.User | None = None, project: M.Project | None = None): '''Return whether the given user has the permission name on the given object. - First, all the roles for a user in the given project context are computed. @@ -341,13 +358,8 @@ def has_access(obj, permission, user=None, project=None): user_id=user._id, project_id=project._id).reaching_ids # TODO: move deny logic into loop below; see ticket [#6715] - if user != M.User.anonymous(): - user_roles = Credentials.get().user_roles(user_id=user._id, - project_id=project.root_project._id) - for r in user_roles: - deny_user = M.ACE.deny(r['_id'], permission) - if M.ACL.contains(deny_user, obj.acl): - return False + if is_denied(obj, permission, user, project): + return False chainable_roles = [] for rid in roles: diff --git a/Allura/allura/model/discuss.py b/Allura/allura/model/discuss.py index 3ecb42907..92999e295 100644 --- a/Allura/allura/model/discuss.py +++ b/Allura/allura/model/discuss.py @@ -33,10 +33,11 @@ from ming.odm.property import (FieldProperty, RelationProperty, ForeignIdProperty) from ming.utils import LazyProperty from bson import ObjectId +from webob import exc from allura.lib import helpers as h from allura.lib import security -from allura.lib.security import require_access, has_access +from allura.lib.security import require_access, has_access, is_denied from allura.lib import utils from allura.model.notification import Notification, Mailbox from .artifact import Artifact, ArtifactReference, VersionedArtifact, Snapshot, Message, Feed, ReactableArtifact @@ -335,6 +336,9 @@ class Thread(Artifact, ActivityObject): is_meta=False, subscribe=False, **kw): if not ignore_security: require_access(self, 'post') + # check app-level for Blocked Users, in addition to the standard `self` check above + if is_denied(self.app, 'post', c.user, self.project): + raise exc.HTTPForbidden if subscribe: self.primary().subscribe() if message_id is None: diff --git a/ForgeDiscussion/forgediscussion/controllers/root.py b/ForgeDiscussion/forgediscussion/controllers/root.py index 212245f79..251d57923 100644 --- a/ForgeDiscussion/forgediscussion/controllers/root.py +++ b/ForgeDiscussion/forgediscussion/controllers/root.py @@ -110,9 +110,13 @@ class RootController(BaseController, DispatchIndex, FeedController): @with_trailing_slash @expose('jinja:forgediscussion:templates/discussionforums/create_topic.html') def create_topic(self, forum_name=None, new_forum=False, **kw): - forums = model.Forum.query.find(dict(app_config_id=c.app.config._id, - parent_id=None, - deleted=False)) + # check app-level access to guarantee enforcement of Block Users. In addition to per-forum checks below + if has_access(c.app, 'post'): + forums = model.Forum.query.find(dict(app_config_id=c.app.config._id, + parent_id=None, + deleted=False)) + else: + forums = [] c.new_topic = self.W.new_topic my_forums = [] forum_name = h.really_unicode(unquote(forum_name)) if forum_name else None diff --git a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py index 22fea58a3..9be5d9c4b 100644 --- a/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py +++ b/ForgeDiscussion/forgediscussion/tests/functional/test_forum_admin.py @@ -234,8 +234,7 @@ class TestForumAdmin(TestController): form.submit() # try to post in the forum and get a 403 r = self.app.get('/discussion/create_topic/') - f = r.html.find( - 'form', {'action': '/p/test/discussion/save_new_topic'}) + f = r.html.find('form', {'action': '/p/test/discussion/save_new_topic'}) params = dict() inputs = f.findAll('input') for field in inputs: @@ -272,6 +271,20 @@ class TestForumAdmin(TestController): r = self.app.post('/discussion/save_new_topic', params=params) assert 'http://localhost/p/test/discussion/testforum/thread/' in r.location + # and make sure a blocked user takes priority over anonymous-allowed letting them through + r = self.app.post('/admin/discussion/block_user', + params={'username': 'test-user', 'perm': 'post', 'reason': 'They are bad'}) + # forum not listed on create topic page + r = self.app.get('/discussion/create_topic/', + extra_environ=dict(username='test-user')) + r.mustcontain('You do not have permission to post in any forums') + # if you try to post anyway, not allowed + r = self.app.post('/discussion/save_new_topic', + params=params, + extra_environ=dict(username='test-user'), + status=403) + + def test_footer_monitoring_email(self): r = self.app.get('/admin/discussion/forums') form = r.forms['add-forum']