Browse Source

Implement PR bug finds

Mostly, fix the busted User/Group get permissions implementation
Alec Nikolas Reiter 9 years ago
parent
commit
bbfd652c11

+ 0 - 3
flaskbb/forum/models.py

@@ -14,7 +14,6 @@ from flask import url_for, abort
 from sqlalchemy.orm import aliased
 from sqlalchemy.orm import aliased
 
 
 from flaskbb.extensions import db
 from flaskbb.extensions import db
-from flaskbb.utils.decorators import can_access_forum, can_access_topic
 from flaskbb.utils.helpers import slugify, get_categories_and_forums, \
 from flaskbb.utils.helpers import slugify, get_categories_and_forums, \
     get_forums
     get_forums
 from flaskbb.utils.database import CRUDMixin
 from flaskbb.utils.database import CRUDMixin
@@ -324,7 +323,6 @@ class Topic(db.Model, CRUDMixin):
         return "<{} {}>".format(self.__class__.__name__, self.id)
         return "<{} {}>".format(self.__class__.__name__, self.id)
 
 
     @classmethod
     @classmethod
-    @can_access_topic
     def get_topic(cls, topic_id, user):
     def get_topic(cls, topic_id, user):
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
         topic = Topic.query.filter_by(id=topic_id).first_or_404()
         return topic
         return topic
@@ -805,7 +803,6 @@ class Forum(db.Model, CRUDMixin):
 
 
     # Classmethods
     # Classmethods
     @classmethod
     @classmethod
-    @can_access_forum
     def get_forum(cls, forum_id, user):
     def get_forum(cls, forum_id, user):
         """Returns the forum and forumsread object as a tuple for the user.
         """Returns the forum and forumsread object as a tuple for the user.
 
 

+ 31 - 15
flaskbb/forum/views.py

@@ -11,26 +11,39 @@
 """
 """
 import datetime
 import datetime
 
 
-from flask import (Blueprint, redirect, url_for, current_app,
-                   request, flash)
+from flask import Blueprint, redirect, url_for, current_app, request, flash
 from flask_login import login_required, current_user
 from flask_login import login_required, current_user
 from flask_babelex import gettext as _
 from flask_babelex import gettext as _
 from flask_allows import Permission, And
 from flask_allows import Permission, And
-from flaskbb.extensions import db
+from flaskbb.extensions import db, allows
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
-from flaskbb.utils.helpers import (get_online_users, time_diff, format_quote,
-                                   render_template, do_topic_action)
+from flaskbb.utils.helpers import (
+    get_online_users, time_diff, format_quote, render_template, do_topic_action
+)
 
 
-from flaskbb.utils.requirements import (CanPostReply, CanPostTopic,
-                                        IsAtleastModeratorInForum,
-                                        CanDeleteTopic, CanEditPost,
-                                        CanDeletePost)
+from flaskbb.utils.requirements import (
+    CanAccessForum,
+    CanAccessTopic,
+    CanDeletePost,
+    CanDeleteTopic,
+    CanEditPost,
+    CanPostReply,
+    CanPostTopic,
+    IsAtleastModeratorInForum,
+)
 
 
 
 
-from flaskbb.forum.models import (Category, Forum, Topic, Post, ForumsRead,
-                                  TopicsRead)
-from flaskbb.forum.forms import (QuickreplyForm, ReplyForm, NewTopicForm,
-                                 ReportForm, UserSearchForm, SearchPageForm)
+from flaskbb.forum.models import (
+    Category, Forum, Topic, Post, ForumsRead, TopicsRead
+)
+from flaskbb.forum.forms import (
+    NewTopicForm,
+    QuickreplyForm,
+    ReplyForm,
+    ReportForm,
+    SearchPageForm,
+    UserSearchForm,
+)
 from flaskbb.user.models import User
 from flaskbb.user.models import User
 
 
 forum = Blueprint("forum", __name__)
 forum = Blueprint("forum", __name__)
@@ -79,11 +92,13 @@ def view_category(category_id, slug=None):
 
 
 @forum.route("/forum/<int:forum_id>")
 @forum.route("/forum/<int:forum_id>")
 @forum.route("/forum/<int:forum_id>-<slug>")
 @forum.route("/forum/<int:forum_id>-<slug>")
+@allows.requires(CanAccessForum())
 def view_forum(forum_id, slug=None):
 def view_forum(forum_id, slug=None):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 
-    forum_instance, forumsread = Forum.get_forum(forum_id=forum_id,
-                                                 user=current_user)
+    forum_instance, forumsread = Forum.get_forum(
+        forum_id=forum_id, user=current_user
+    )
 
 
     if forum_instance.external:
     if forum_instance.external:
         return redirect(forum_instance.external)
         return redirect(forum_instance.external)
@@ -101,6 +116,7 @@ def view_forum(forum_id, slug=None):
 
 
 @forum.route("/topic/<int:topic_id>", methods=["POST", "GET"])
 @forum.route("/topic/<int:topic_id>", methods=["POST", "GET"])
 @forum.route("/topic/<int:topic_id>-<slug>", methods=["POST", "GET"])
 @forum.route("/topic/<int:topic_id>-<slug>", methods=["POST", "GET"])
+@allows.requires(CanAccessTopic())
 def view_topic(topic_id, slug=None):
 def view_topic(topic_id, slug=None):
     page = request.args.get('page', 1, type=int)
     page = request.args.get('page', 1, type=int)
 
 

+ 25 - 34
flaskbb/management/forms.py

@@ -9,21 +9,27 @@
     :license: BSD, see LICENSE for more details.
     :license: BSD, see LICENSE for more details.
 """
 """
 from flask_wtf import Form
 from flask_wtf import Form
-from wtforms import (StringField, TextAreaField, PasswordField, IntegerField,
-                     BooleanField, SelectField, SubmitField,
-		     HiddenField)
-from wtforms.validators import (DataRequired, Optional, Email, regexp, Length,
-                                URL, ValidationError)
-from wtforms.ext.sqlalchemy.fields import (QuerySelectField,
-                                           QuerySelectMultipleField)
+from wtforms import (
+    BooleanField, HiddenField, IntegerField, PasswordField,
+    SelectField, StringField, SubmitField, TextAreaField,
+)
+from wtforms.validators import (
+    DataRequired, Optional, Email, regexp, Length, URL, ValidationError
+)
+from wtforms.ext.sqlalchemy.fields import (
+    QuerySelectField, QuerySelectMultipleField
+)
 from sqlalchemy.orm.session import make_transient, make_transient_to_detached
 from sqlalchemy.orm.session import make_transient, make_transient_to_detached
 from flask_babelex import lazy_gettext as _
 from flask_babelex import lazy_gettext as _
 
 
 from flaskbb.utils.fields import BirthdayField
 from flaskbb.utils.fields import BirthdayField
-from flaskbb.utils.widgets import SelectBirthdayWidget, MultiSelect
+from flaskbb.utils.widgets import SelectBirthdayWidget
 from flaskbb.extensions import db
 from flaskbb.extensions import db
 from flaskbb.forum.models import Forum, Category
 from flaskbb.forum.models import Forum, Category
 from flaskbb.user.models import User, Group
 from flaskbb.user.models import User, Group
+from flaskbb.utils.requirements import IsAtleastModerator
+from flask_allows import Permission
+
 
 
 USERNAME_RE = r'^[\w.+-]+$'
 USERNAME_RE = r'^[\w.+-]+$'
 is_username = regexp(USERNAME_RE,
 is_username = regexp(USERNAME_RE,
@@ -337,35 +343,20 @@ class ForumForm(Form):
                                     "moderators."))
                                     "moderators."))
 
 
     def validate_moderators(self, field):
     def validate_moderators(self, field):
-        approved_moderators = list()
+        approved_moderators = []
 
 
         if field.data:
         if field.data:
-            # convert the CSV string in a list
-            moderators = field.data.split(",")
-            # remove leading and ending spaces
-            moderators = [mod.strip() for mod in moderators]
-            for moderator in moderators:
-                # Check if the usernames exist
-                user = User.query.filter_by(username=moderator).first()
-
-                # Check if the user has the permissions to moderate a forum
-                if user:
-                    if not (user.get_permissions()["mod"] or
-                            user.get_permissions()["admin"] or
-                            user.get_permissions()["super_mod"]):
-                        raise ValidationError(
-                            _("%(user)s is not in a moderators group.",
-                              user=user.username)
-                        )
-                    else:
-                        approved_moderators.append(user)
+            moderators = [mod.strip() for mod in field.data.split(',')]
+            users = User.query.filter(User.username.in_(moderators))
+            for user in users:
+                if not Permission(IsAtleastModerator, identity=user):
+                    raise ValidationError(
+                        _("%(user)s is not in a moderators group.",
+                            user=user.username)
+                    )
                 else:
                 else:
-                    raise ValidationError(_("User %(moderator)s not found.",
-                                            moderator=moderator))
-            field.data = approved_moderators
-
-        else:
-            field.data = approved_moderators
+                    approved_moderators.append(user)
+        field.data = approved_moderators
 
 
     def save(self):
     def save(self):
         data = self.data
         data = self.data

+ 19 - 15
flaskbb/management/views.py

@@ -17,14 +17,15 @@ from flask import (Blueprint, current_app, request, redirect, url_for, flash,
 from flask_login import current_user
 from flask_login import current_user
 from flask_plugins import get_all_plugins, get_plugin, get_plugin_from_all
 from flask_plugins import get_all_plugins, get_plugin, get_plugin_from_all
 from flask_babelex import gettext as _
 from flask_babelex import gettext as _
-from flask_allows import Permission
+from flask_allows import Permission, Not
 
 
 from flaskbb import __version__ as flaskbb_version
 from flaskbb import __version__ as flaskbb_version
 from flaskbb._compat import iteritems
 from flaskbb._compat import iteritems
 from flaskbb.forum.forms import UserSearchForm
 from flaskbb.forum.forms import UserSearchForm
 from flaskbb.utils.settings import flaskbb_config
 from flaskbb.utils.settings import flaskbb_config
-from flaskbb.utils.requirements import (IsAtleastModerator, IsAdmin,
-                                        CanBanUser, CanEditUser)
+from flaskbb.utils.requirements import (
+    IsAtleastModerator, IsAdmin, CanBanUser, CanEditUser, IsAtleastSuperModerator
+)
 from flaskbb.extensions import db, allows
 from flaskbb.extensions import db, allows
 from flaskbb.utils.helpers import render_template, time_diff, get_online_users
 from flaskbb.utils.helpers import render_template, time_diff, get_online_users
 from flaskbb.user.models import Guest, User, Group
 from flaskbb.user.models import Guest, User, Group
@@ -138,7 +139,7 @@ def users():
 def edit_user(user_id):
 def edit_user(user_id):
     user = User.query.filter_by(id=user_id).first_or_404()
     user = User.query.filter_by(id=user_id).first_or_404()
 
 
-    if not Permission(CanEditUser):
+    if not Permission(CanEditUser, identity=current_user):
         flash(_("You are not allowed to edit this user."), "danger")
         flash(_("You are not allowed to edit this user."), "danger")
         return redirect(url_for("management.users"))
         return redirect(url_for("management.users"))
 
 
@@ -147,12 +148,15 @@ def edit_user(user_id):
 
 
     filt = db.or_(Group.id.in_(g.id for g in current_user.groups), member_group)
     filt = db.or_(Group.id.in_(g.id for g in current_user.groups), member_group)
 
 
-    if any(current_user.permissions[p] for p in ['super_mod', 'admin']):
+    if Permission(IsAtleastSuperModerator, identity=current_user):
         filt = db.or_(filt, Group.mod)
         filt = db.or_(filt, Group.mod)
 
 
-    if current_user.permissions['admin']:
+    if Permission(IsAdmin, identity=current_user):
         filt = db.or_(filt, Group.admin, Group.super_mod)
         filt = db.or_(filt, Group.admin, Group.super_mod)
 
 
+    if Permission(CanBanUser, identity=current_user):
+        filt = db.or_(filt, Group.banned)
+
     group_query = Group.query.filter(filt)
     group_query = Group.query.filter(filt)
 
 
     form = EditUserForm(user)
     form = EditUserForm(user)
@@ -254,7 +258,7 @@ def banned_users():
 @management.route("/users/<int:user_id>/ban", methods=["POST"])
 @management.route("/users/<int:user_id>/ban", methods=["POST"])
 @allows.requires(IsAtleastModerator)
 @allows.requires(IsAtleastModerator)
 def ban_user(user_id=None):
 def ban_user(user_id=None):
-    if not Permission(CanBanUser):
+    if not Permission(CanBanUser, identity=current_user):
         flash(_("You do not have the permissions to ban this user."), "danger")
         flash(_("You do not have the permissions to ban this user."), "danger")
         return redirect(url_for("management.overview"))
         return redirect(url_for("management.overview"))
 
 
@@ -267,10 +271,11 @@ def ban_user(user_id=None):
         for user in users:
         for user in users:
             # don't let a user ban himself and do not allow a moderator to ban
             # don't let a user ban himself and do not allow a moderator to ban
             # a admin user
             # a admin user
-            if current_user.id == user.id or \
-                    user.get_permissions()['admin'] and \
-                    (current_user.permissions['mod'] or
-                     current_user.permissions['super_mod']):
+            if (
+                current_user.id == user.id or
+                Permission(IsAdmin, identity=user)
+                and Permission(Not(IsAdmin), current_user)
+            ):
                 continue
                 continue
 
 
             elif user.ban():
             elif user.ban():
@@ -293,9 +298,8 @@ def ban_user(user_id=None):
     user = User.query.filter_by(id=user_id).first_or_404()
     user = User.query.filter_by(id=user_id).first_or_404()
 
 
     # Do not allow moderators to ban admins
     # Do not allow moderators to ban admins
-    if user.get_permissions()['admin'] and \
-            (current_user.permissions['mod'] or
-             current_user.permissions['super_mod']):
+    if Permission(IsAdmin, identity=user) and \
+       Permission(Not(IsAdmin), identity=current_user):
 
 
         flash(_("A moderator cannot ban an admin user."), "danger")
         flash(_("A moderator cannot ban an admin user."), "danger")
         return redirect(url_for("management.overview"))
         return redirect(url_for("management.overview"))
@@ -312,7 +316,7 @@ def ban_user(user_id=None):
 @management.route("/users/<int:user_id>/unban", methods=["POST"])
 @management.route("/users/<int:user_id>/unban", methods=["POST"])
 @allows.requires(IsAtleastModerator)
 @allows.requires(IsAtleastModerator)
 def unban_user(user_id=None):
 def unban_user(user_id=None):
-    if not Permission(CanBanUser):
+    if not Permission(CanBanUser, identity=current_user):
         flash(_("You do not have the permissions to unban this user."),
         flash(_("You do not have the permissions to unban this user."),
               "danger")
               "danger")
         return redirect(url_for("management.overview"))
         return redirect(url_for("management.overview"))

+ 2 - 0
flaskbb/templates/forum/forum.html

@@ -124,10 +124,12 @@
         </div>
         </div>
     </div>
     </div>
 
 
+    {% if current_user|can_moderate(forum) %}
     <div class="row controls-row">
     <div class="row controls-row">
         <a class="btn btn-default" href="{{ url_for('forum.manage_forum', forum_id=forum.id, slug=forum.slug) }}">
         <a class="btn btn-default" href="{{ url_for('forum.manage_forum', forum_id=forum.id, slug=forum.slug) }}">
             <span class="fa fa-tasks"></span> {% trans %}Moderation Mode{% endtrans %}
             <span class="fa fa-tasks"></span> {% trans %}Moderation Mode{% endtrans %}
         </a>
         </a>
     </div>
     </div>
+    {% endif %}
 </div>
 </div>
 {% endblock %}
 {% endblock %}

+ 2 - 2
flaskbb/user/models.py

@@ -362,7 +362,7 @@ class User(db.Model, UserMixin, CRUDMixin):
         for group in self.groups:
         for group in self.groups:
             columns = set(group.__table__.columns.keys()) - set(exclude)
             columns = set(group.__table__.columns.keys()) - set(exclude)
             for c in columns:
             for c in columns:
-                perms[c] = getattr(group, c)
+                perms[c] = getattr(group, c) or perms.get(c, False)
         return perms
         return perms
 
 
     @cache.memoize(timeout=max_integer)
     @cache.memoize(timeout=max_integer)
@@ -499,7 +499,7 @@ class Guest(AnonymousUserMixin):
         for group in self.groups:
         for group in self.groups:
             columns = set(group.__table__.columns.keys()) - set(exclude)
             columns = set(group.__table__.columns.keys()) - set(exclude)
             for c in columns:
             for c in columns:
-                perms[c] = getattr(group, c)
+                perms[c] = getattr(group, c) or perms.get(c, False)
         return perms
         return perms
 
 
     @classmethod
     @classmethod

+ 0 - 88
flaskbb/utils/decorators.py

@@ -1,88 +0,0 @@
-# -*- coding: utf-8 -*-
-"""
-    flaskbb.utils.decorators
-    ~~~~~~~~~~~~~~~~~~~~~~~~
-
-    A place for our decorators.
-
-    :copyright: (c) 2014 by the FlaskBB Team.
-    :license: BSD, see LICENSE for more details.
-"""
-from functools import wraps
-
-from flask import abort
-from flask_login import current_user
-
-
-def admin_required(f):
-    @wraps(f)
-    def decorated(*args, **kwargs):
-        if current_user.is_anonymous():
-            abort(403)
-        if not current_user.permissions['admin']:
-            abort(403)
-        return f(*args, **kwargs)
-    return decorated
-
-
-def moderator_required(f):
-    @wraps(f)
-    def decorated(*args, **kwargs):
-        if current_user.is_anonymous():
-            abort(403)
-
-        if not any([current_user.permissions['admin'],
-                    current_user.permissions['super_mod'],
-                    current_user.permissions['mod']]):
-            abort(403)
-
-        return f(*args, **kwargs)
-    return decorated
-
-
-def can_access_forum(func):
-    def decorated(*args, **kwargs):
-        forum_id = kwargs['forum_id'] if 'forum_id' in kwargs else args[1]
-        from flaskbb.forum.models import Forum
-        from flaskbb.user.models import Group
-
-        # get list of user group ids
-        if current_user.is_authenticated():
-            user_groups = [gr.id for gr in current_user.groups]
-        else:
-            user_groups = [Group.get_guest_group().id]
-
-        user_forums = Forum.query.filter(
-            Forum.id == forum_id, Forum.groups.any(Group.id.in_(user_groups))
-        ).all()
-
-        if len(user_forums) < 1:
-            abort(403)
-
-        return func(*args, **kwargs)
-    return decorated
-
-
-def can_access_topic(func):
-    def decorated(*args, **kwargs):
-        topic_id = kwargs['topic_id'] if 'topic_id' in kwargs else args[1]
-        from flaskbb.forum.models import Forum, Topic
-        from flaskbb.user.models import Group
-
-        topic = Topic.query.filter_by(id=topic_id).first()
-        # get list of user group ids
-        if current_user.is_authenticated():
-            user_groups = [gr.id for gr in current_user.groups]
-        else:
-            user_groups = [Group.get_guest_group().id]
-
-        user_forums = Forum.query.filter(
-            Forum.id == topic.forum.id,
-            Forum.groups.any(Group.id.in_(user_groups))
-        ).all()
-
-        if len(user_forums) < 1:
-            abort(403)
-
-        return func(*args, **kwargs)
-    return decorated

+ 23 - 0
flaskbb/utils/requirements.py

@@ -11,6 +11,7 @@
 from flask_allows import Requirement, Or, And
 from flask_allows import Requirement, Or, And
 from flaskbb.exceptions import FlaskBBError
 from flaskbb.exceptions import FlaskBBError
 from flaskbb.forum.models import Post, Topic, Forum
 from flaskbb.forum.models import Post, Topic, Forum
+from flaskbb.user.models import Group
 
 
 
 
 class Has(Requirement):
 class Has(Requirement):
@@ -162,6 +163,28 @@ class ForumNotLocked(Requirement):
             return Forum.query.get(view_args['forum_id'])
             return Forum.query.get(view_args['forum_id'])
 
 
 
 
+class CanAccessForum(Requirement):
+    def fulfill(self, user, request):
+        forum_id = request.view_args['forum_id']
+        group_ids = [g.id for g in user.groups]
+
+        return Forum.query.filter(
+            Forum.id == forum_id,
+            Forum.groups.any(Group.id.in_(group_ids))
+        ).count()
+
+
+class CanAccessTopic(Requirement):
+    def fulfill(self, user, request):
+        topic_id = request.view_args['topic_id']
+        group_ids = [g.id for g in user.groups]
+
+        return Forum.query.join(Topic, Topic.forum_id == Forum.id).filter(
+            Topic.id == topic_id,
+            Forum.groups.any(Group.id.in_(group_ids))
+        ).count()
+
+
 def IsAtleastModeratorInForum(forum_id=None, forum=None):
 def IsAtleastModeratorInForum(forum_id=None, forum=None):
     return Or(IsAtleastSuperModerator, IsModeratorInForum(forum_id=forum_id,
     return Or(IsAtleastSuperModerator, IsModeratorInForum(forum_id=forum_id,
                                                           forum=forum))
                                                           forum=forum))