Browse Source

Add initial requirements and tests

Alec Nikolas Reiter 9 years ago
parent
commit
8fef22aa24
2 changed files with 231 additions and 0 deletions
  1. 147 0
      flaskbb/utils/requirements.py
  2. 84 0
      tests/unit/test_requirements.py

+ 147 - 0
flaskbb/utils/requirements.py

@@ -0,0 +1,147 @@
+"""
+    flaskbb.utils.requirements
+    ~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+    Authorization requirements for FlaskBB.
+
+    :copyright: (c) 2015 by the FlaskBB Team.
+    :license: BSD, see LICENSE for more details
+"""
+
+from flask_allows import Requirement, Or, And
+from flaskbb.exceptions import FlaskBBError
+from flaskbb.forum.models import Post, Topic, Forum
+
+
+class IsAuthed(Requirement):
+    def fulfill(self, user, request):
+        return user.is_authenticated()
+
+
+class IsMod(IsAuthed):
+    def fulfill(self, user, request):
+        return (super(IsMod, self).fulfill(user, request) and
+                user.permissions.get('mod'))
+
+
+class IsSuperMod(IsAuthed):
+    def fulfill(self, user, request):
+        return (super(IsSuperMod, self).fulfill(user, request) and
+                user.permissions.get('super_mod'))
+
+
+class IsAdmin(IsAuthed):
+    def fulfill(self, user, request):
+        return (super(IsAdmin, self).fulfill(user, request) and
+                user.permissions.get('admin'))
+
+
+class IsModeratorInForum(IsAuthed):
+    def fulfill(self, user, request):
+        moderators = self._get_forum_moderators(request)
+        return (super(IsModeratorInForum, self).fulfill(user, request) and
+                self._user_is_forum_moderator(user, moderators))
+
+    def _user_is_forum_moderator(self, user, moderators):
+        return user in moderators
+
+    def _get_forum_moderators(self, request):
+        return self._get_forum_from_request(request).moderators
+
+    def _get_forum_from_request(self, request):
+        view_args = request.view_args
+        if 'post_id' in view_args:
+            return Post.query.get(view_args['post_id']).topic.forum
+        elif 'topic_id' in view_args:
+            return Topic.query.get(view_args['topic_id']).forum
+        elif 'forum_id' in view_args:
+            return Forum.query.get(view_args['forum_id'])
+        else:
+            raise FlaskBBError
+
+
+class IsSameUser(IsAuthed):
+    def fulfill(self, user, request):
+        return (super(IsSameUser, self).fulfill(user, request) and
+                user.id == self._get_user_id_from_post(request))
+
+    def _get_user_id_from_post(self, request):
+        view_args = request.view_args
+        if 'post_id' in view_args:
+            return Post.query.get(view_args['post_id']).user_id
+        elif 'topic_id' in view_args:
+            return Topic.query.get(view_args['topic_id']).user_id
+        else:
+            raise FlaskBBError
+
+
+class Has(Requirement):
+    def __init__(self, permission):
+        self.permission = permission
+
+    def fulfill(self, user, request):
+        return user.permissions.get(self.permission)
+
+
+class TopicNotLocked(Requirement):
+    def fulfill(self, user, request):
+        return not self._is_topic_or_forum_locked(request)
+
+    def _get_topic_from_request(self, request):
+        view_args = request.view_args
+        if 'post_id' in view_args:
+            return Post.query.get(view_args['post_id']).topic
+        elif 'topic_id' in view_args:
+            return Topic.query.get(view_args['topic_id'])
+        else:
+            raise FlaskBBError
+
+    def _is_topic_or_forum_locked(self, request):
+        topic = self._get_topic_from_request(request)
+        return topic.locked or topic.forum.locked
+
+
+class ForumNotLocked(Requirement):
+    def fulfill(self, user, request):
+        return not self._is_forum_locked(request)
+
+    def _is_forum_locked(self, request):
+        return self._get_forum_from_request(request).locked
+
+    def _get_forum_from_request(self, request):
+        view_args = request.view_args
+        if 'post_id' in view_args:
+            return Post.query.get(view_args['post_id']).topic.forum
+        elif 'topic_id' in view_args:
+            return Topic.query.get(view_args['topic_id']).forum
+        elif 'forum_id' in view_args:
+            return Forum.query.get(view_args['forum_id'])
+
+
+IsAtleastModerator = Or(IsAdmin(), IsSuperMod(), IsMod())
+
+IsAtleastSuperModerator = Or(IsAdmin(), IsSuperMod())
+
+CanBanUser = Or(IsAtleastSuperModerator, Has('mod_banuser'))
+
+CanEditUser = Or(IsAtleastSuperModerator, Has('mod_edituser'))
+
+CanEditPost = Or(And(IsSameUser(), Has('editpost'), TopicNotLocked()),
+                 IsAtleastSuperModerator,
+                 And(IsModeratorInForum(), Has('editpost')))
+
+CanDeletePost = Or(And(IsSameUser(), Has('editpost'), TopicNotLocked()),
+                   IsAtleastSuperModerator,
+                   And(IsModeratorInForum(), Has('editpost')))
+
+CanPostReply = Or(And(Has('postreply'), TopicNotLocked()),
+                  IsModeratorInForum(),
+                  IsAtleastSuperModerator)
+
+CanPostTopic = Or(And(Has('posttopic'), ForumNotLocked()),
+                  IsAtleastSuperModerator,
+                  IsModeratorInForum())
+
+CanDeleteTopic = Or(And(IsSameUser(), Has('deletetopic'), TopicNotLocked()),
+                    IsAtleastSuperModerator,
+                    And(IsModeratorInForum, Has('deletetopic')))

+ 84 - 0
tests/unit/test_requirements.py

@@ -0,0 +1,84 @@
+from flaskbb.utils import requirements as r
+from flaskbb.utils.datastructures import SimpleNamespace
+from flaskbb.user.models import User
+
+
+def test_Fred_IsNotAdmin(Fred):
+    assert not r.IsAdmin().fulfill(Fred, None)
+
+
+def test_IsAdmin_with_admin(admin_user):
+    assert r.IsAdmin().fulfill(admin_user, None)
+
+
+def test_IsAtleastModerator_with_mod(moderator_user):
+    assert r.IsAtleastModerator.fulfill(moderator_user, None)
+
+
+def test_IsAtleastModerator_with_supermod(super_moderator_user):
+    assert r.IsAtleastModerator.fulfill(super_moderator_user, None)
+
+
+def test_IsAtleastModerator_with_admin(admin_user):
+    assert r.IsAtleastModerator.fulfill(admin_user, None)
+
+
+def test_IsAtleastSuperModerator_with_not_smod(moderator_user):
+    assert not r.IsAtleastSuperModerator.fulfill(moderator_user, None)
+
+
+def test_CanBanUser_with_admin(admin_user):
+    assert r.CanBanUser.fulfill(admin_user, None)
+
+
+def test_CanBanUser_with_smod(super_moderator_user):
+    assert r.CanBanUser.fulfill(super_moderator_user, None)
+
+
+def test_CanBanUser_with_mod(moderator_user):
+    assert r.CanBanUser.fulfill(moderator_user, None)
+
+
+def test_Fred_CannotBanUser(Fred):
+    assert not r.CanBanUser.fulfill(Fred, None)
+
+
+def test_CanEditTopic_with_member(user, topic):
+    request = SimpleNamespace(view_args={'topic_id': topic.id})
+    assert r.CanEditPost.fulfill(user, request)
+
+
+def test_Fred_cannot_edit_other_members_post(user, Fred, topic):
+    request = SimpleNamespace(view_args={'topic_id': topic.id})
+    assert not r.CanEditPost(Fred, request)
+
+
+def test_Fred_CannotEditLockedTopic(Fred, topic_locked):
+    request = SimpleNamespace(view_args={'topic_id': topic_locked.id})
+    assert not r.CanEditPost.fulfill(Fred, request)
+
+
+def test_Moderator_in_Forum_CanEditLockedTopic(moderator_user, topic_locked):
+    request = SimpleNamespace(view_args={'topic_id': topic_locked.id})
+    assert r.CanEditPost.fulfill(moderator_user, request)
+
+
+def test_FredIsAMod_but_still_cant_edit_topic_in_locked_forum(Fred, topic_locked, default_groups):
+    request = SimpleNamespace(view_args={'topic_id': topic_locked.id})
+    Fred.primary_group = default_groups[2]
+    assert not r.CanEditPost.fulfill(Fred, request)
+
+
+def test_Fred_cannot_reply_to_locked_topic(Fred, topic_locked):
+    request = SimpleNamespace(view_args={'topic_id': topic_locked.id})
+    assert not r.CanPostReply.fulfill(Fred, request)
+
+
+def test_Fred_cannot_delete_others_post(Fred, topic):
+    request = SimpleNamespace(view_args={'post_id': topic.first_post.id})
+    assert not r.CanDeletePost.fulfill(Fred, request)
+
+
+def test_Mod_can_delete_others_post(moderator_user, topic):
+    request = SimpleNamespace(view_args={'post_id': topic.first_post.id})
+    assert r.CanDeletePost.fulfill(moderator_user, request)