class InstallPlugin(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify plugins"), level="danger", endpoint="management.overview" ) ) ] def post(self, name): plugin_module = validate_plugin(name) plugin = PluginRegistry.query.filter_by(name=name).first_or_404() if not plugin.enabled: flash( _( "Can't install plugin. Enable '%(plugin)s' plugin first.", plugin=plugin.name ), "danger" ) return redirect(url_for("management.plugins")) plugin.add_settings(plugin_module.SETTINGS) flash(_("Plugin has been installed."), "success") return redirect(url_for("management.plugins"))
class DeletePost(MethodView): decorators = [ login_required, allows.requires( CanDeletePost, on_fail=FlashAndRedirect( message=_("You are not allowed to delete this post"), level="danger", endpoint=lambda *a, **k: current_topic.url ) ), ] def post(self, post_id): post = Post.query.filter_by(id=post_id).first_or_404() first_post = post.first_post topic_url = post.topic.url forum_url = post.topic.forum.url post.delete() # If the post was the first post in the topic, redirect to the forums if first_post: return redirect(forum_url) return redirect(topic_url)
class LogDownload(Hub): decorators = [ allows.requires( CanAccessServerHub(), on_fail=FlashAndRedirect( message=_("You are not allowed to access the hub"), level="danger", endpoint="forum.index" ) ) ] def get(self): server_id = request.args["server"] path = request.args["path"] servers = current_app.config["BYOND_SERVERS"] assert path assert server_id server = None for srv in servers: if srv.id == server_id: server = srv break if server is None: abort(404) file_path = os.path.join(server.logs_path, path) return send_file(file_path, as_attachment=True)
class NewTopic(MethodView): decorators = [ login_required, allows.requires( CanAccessForum(), CanPostTopic, on_fail=FlashAndRedirect( message=_("You are not allowed to post a topic here"), level="warning", endpoint=lambda *a, **k: current_forum.url ) ), ] def get(self, forum_id, slug=None): forum_instance = Forum.query.filter_by(id=forum_id).first_or_404() return render_template( "forum/new_topic.html", forum=forum_instance, form=self.form() ) def post(self, forum_id, slug=None): forum_instance = Forum.query.filter_by(id=forum_id).first_or_404() form = self.form() if form.validate_on_submit(): topic = form.save(real(current_user), forum_instance) return redirect(url_for("forum.view_topic", topic_id=topic.id)) return render_template( "forum/new_topic.html", forum=forum_instance, form=form ) def form(self): current_app.pluggy.hook.flaskbb_form_new_topic(form=NewTopicForm) return NewTopicForm()
class DeleteReport(MethodView): decorators = [ allows.requires(IsAtleastModerator, on_fail=FlashAndRedirect( message=_("You are not allowed to view reports."), level="danger", endpoint="management.overview")) ] def post(self, report_id=None): if request.is_xhr: ids = request.get_json()["ids"] data = [] for report in Report.query.filter(Report.id.in_(ids)).all(): if report.delete(): data.append({ "id": report.id, "type": "delete", "reverse": False, "reverse_name": None, "reverse_url": None }) return jsonify(message="{} reports deleted.".format(len(data)), category="success", data=data, status=200) report = Report.query.filter_by(id=report_id).first_or_404() report.delete() flash(_("Report deleted."), "success") return redirect(url_for("management.reports"))
class ConfigsView(Hub): decorators = [ allows.requires( CanAccessServerHubAdditional(), on_fail=FlashAndRedirect( message=_("You are not allowed to access the hub"), level="danger", endpoint="forum.index" ) ) ] def get(self): server_id = request.args["server"] servers = current_app.config["BYOND_SERVERS"] for server in servers: if server.id == server_id: config_folder_entries = [os.path.join(server.configs_path, f) for f in os.listdir(server.configs_path)] config_files = [f for f in config_folder_entries if os.path.isfile(f)] config_files_names = [os.path.split(f)[1] for f in config_files] config_files_names = [f for f in config_files_names if f not in server.configs_exclude] config_files_names.sort() return render_template("hub/configs.html", **self.get_args(), configs=config_files_names) return render_template("hub/configs.html", **self.get_args())
class EditCategory(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify categories"), level="danger", endpoint="management.overview")) ] form = CategoryForm def get(self, category_id): category = Category.query.filter_by(id=category_id).first_or_404() form = self.form(obj=category) return render_template('management/category_form.html', form=form, title=_('Edit Category')) def post(self, category_id): category = Category.query.filter_by(id=category_id).first_or_404() form = self.form(obj=category) if form.validate_on_submit(): form.populate_obj(category) flash(_('Category updated.'), 'success') category.save() return render_template('management/category_form.html', form=form, title=_('Edit Category'))
class DeleteGroup(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify groups."), level="danger", endpoint="management.overview" ) ) ] def post(self, group_id=None): if group_id is not None: if group_id <= 6: # there are 6 standard groups flash( _( "You cannot delete the standard groups. " "Try renaming it instead.", "danger" ) ) return redirect(url_for("management.groups")) group = Group.query.filter_by(id=group_id).first_or_404() group.delete() flash(_("Group deleted."), "success") return redirect(url_for("management.groups")) flash(_("No group chosen."), "danger") return redirect(url_for("management.groups"))
class EditGroup(MethodView): decorators = [ allows.requires(IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify groups."), level="danger", endpoint="management.overview")) ] form = EditGroupForm def get(self, group_id): group = Group.query.filter_by(id=group_id).first_or_404() form = self.form(group) return render_template('management/group_form.html', form=form, title=_('Edit Group')) def post(self, group_id): group = Group.query.filter_by(id=group_id).first_or_404() form = EditGroupForm(group) if form.validate_on_submit(): form.populate_obj(group) group.save() if group.guest: Guest.invalidate_cache() flash(_('Group updated.'), 'success') return redirect(url_for('management.groups', group_id=group.id)) return render_template('management/group_form.html', form=form, title=_('Edit Group'))
class AddCategory(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify categories"), level="danger", endpoint="management.overview" ) ) ] form = CategoryForm def get(self): return render_template( 'management/category_form.html', form=self.form(), title=_('Add Category') ) def post(self): form = self.form() if form.validate_on_submit(): form.save() flash(_('Category added.'), 'success') return redirect(url_for('management.forums')) return render_template( 'management/category_form.html', form=form, title=_('Add Category') )
class BanUser(MethodView): decorators = [ allows.requires(IsAtleastModerator, on_fail=FlashAndRedirect( message=_("You are not allowed to manage users"), level="danger", endpoint="management.overview")) ] def post(self, user_id=None): if not Permission(CanBanUser, identity=current_user): flash(_("You do not have the permissions to ban this user."), "danger") return redirect(url_for("management.overview")) # ajax request if request.is_xhr: ids = request.get_json()["ids"] data = [] users = User.query.filter(User.id.in_(ids)).all() for user in users: # don't let a user ban himself and do not allow a moderator # to ban a admin user if (current_user.id == user.id or Permission(IsAdmin, identity=user) and Permission(Not(IsAdmin), current_user)): continue elif user.ban(): data.append({ "id": user.id, "type": "ban", "reverse": "unban", "reverse_name": _("Unban"), "reverse_url": url_for("management.unban_user", user_id=user.id) }) return jsonify(message="{} users banned.".format(len(data)), category="success", data=data, status=200) user = User.query.filter_by(id=user_id).first_or_404() # Do not allow moderators to ban admins if Permission(IsAdmin, identity=user) and Permission( Not(IsAdmin), identity=current_user): flash(_("A moderator cannot ban an admin user."), "danger") return redirect(url_for("management.overview")) if not current_user.id == user.id and user.ban(): flash(_("User is now banned."), "success") else: flash(_("Could not ban user."), "danger") return redirect(url_for("management.banned_users"))
class ViewPost(MethodView): decorators = [ allows.requires( CanAccessForum(), on_fail=FlashAndRedirect( message=_("You are not allowed to access that topic"), level="warning", endpoint=lambda *a, **k: current_category.url)) ] def get(self, post_id): """Redirects to a post in a topic.""" post = Post.query.filter_by(id=post_id).first_or_404() post_in_topic = Post.query.filter(Post.topic_id == post.topic_id, Post.id <= post_id).order_by( Post.id.asc()).count() page = int( math.ceil(post_in_topic / float(flaskbb_config["POSTS_PER_PAGE"]))) return redirect( url_for("forum.view_topic", topic_id=post.topic.id, slug=post.topic.slug, page=page, _anchor="pid{}".format(post.id)))
class DisablePlugin(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify plugins"), level="danger", endpoint="management.overview" ) ) ] def post(self, name): validate_plugin(name) plugin = PluginRegistry.query.filter_by(name=name).first_or_404() if not plugin.enabled: flash( _("Plugin %(plugin)s is already disabled.", plugin=plugin.name), "info" ) return redirect(url_for("management.plugins")) plugin.enabled = False plugin.save() flash( _( "Plugin %(plugin)s disabled. Please restart FlaskBB now.", plugin=plugin.name ), "success" ) return redirect(url_for("management.plugins"))
class ViewForum(MethodView): decorators = [ allows.requires( CanAccessForum(), on_fail=FlashAndRedirect( message=_("You are not allowed to access that forum"), level="warning", endpoint=lambda *a, **k: current_category.url)) ] def get(self, forum_id, slug=None): page = request.args.get("page", 1, type=int) forum_instance, forumsread = Forum.get_forum(forum_id=forum_id, user=real(current_user)) if forum_instance.external: return redirect(forum_instance.external) topics = Forum.get_topics(forum_id=forum_instance.id, user=real(current_user), page=page, per_page=flaskbb_config["TOPICS_PER_PAGE"]) return render_template( "forum/forum.html", forum=forum_instance, topics=topics, forumsread=forumsread, )
class MarkRead(MethodView): decorators = [ login_required, allows.requires( CanAccessForum(), on_fail=FlashAndRedirect( message=_("You are not allowed to access that forum"), level="warning", endpoint=lambda *a, **k: current_category.url)), ] def post(self, forum_id=None, slug=None): # Mark a single forum as read if forum_id is not None: forum_instance = Forum.query.filter_by(id=forum_id).first_or_404() forumsread = ForumsRead.query.filter_by( user_id=real(current_user).id, forum_id=forum_instance.id).first() TopicsRead.query.filter_by(user_id=real(current_user).id, forum_id=forum_instance.id).delete() if not forumsread: forumsread = ForumsRead() forumsread.user = real(current_user) forumsread.forum = forum_instance forumsread.last_read = time_utcnow() forumsread.cleared = time_utcnow() db.session.add(forumsread) db.session.commit() flash( _("Forum %(forum)s marked as read.", forum=forum_instance.title), "success") return redirect(forum_instance.url) # Mark all forums as read ForumsRead.query.filter_by(user_id=real(current_user).id).delete() TopicsRead.query.filter_by(user_id=real(current_user).id).delete() forums = Forum.query.all() forumsread_list = [] for forum_instance in forums: forumsread = ForumsRead() forumsread.user = real(current_user) forumsread.forum = forum_instance forumsread.last_read = time_utcnow() forumsread.cleared = time_utcnow() forumsread_list.append(forumsread) db.session.add_all(forumsread_list) db.session.commit() flash(_("All forums marked as read."), "success") return redirect(url_for("forum.index"))
class DeleteGroup(MethodView): decorators = [ allows.requires(IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify groups."), level="danger", endpoint="management.overview")) ] def post(self, group_id=None): if request.get_json() is not None: ids = request.get_json().get("ids") if not ids: return jsonify(message="No ids provided.", category="error", status=404) # TODO: Get rid of magic numbers if not (set(ids) & set(["1", "2", "3", "4", "5", "6"])): data = [] for group in Group.query.filter(Group.id.in_(ids)).all(): group.delete() data.append({ "id": group.id, "type": "delete", "reverse": False, "reverse_name": None, "reverse_url": None }) return jsonify(message="{} groups deleted.".format(len(data)), category="success", data=data, status=200) return jsonify( message=_("You cannot delete one of the standard groups."), category="danger", data=None, status=404) if group_id is not None: if group_id <= 6: # there are 6 standard groups flash( _( "You cannot delete the standard groups. " "Try renaming it instead.", "danger")) return redirect(url_for("management.groups")) group = Group.query.filter_by(id=group_id).first_or_404() group.delete() flash(_("Group deleted."), "success") return redirect(url_for("management.groups")) flash(_("No group chosen."), "danger") return redirect(url_for("management.groups"))
class DeleteUser(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to manage users"), level="danger", endpoint="management.overview" ) ) ] def post(self, user_id=None): # ajax request if request.get_json() is not None: ids = request.get_json().get("ids") if not ids: return jsonify( message="No ids provided.", category="error", status=404 ) data = [] for user in User.query.filter(User.id.in_(ids)).all(): # do not delete current user if current_user.id == user.id: continue if user.delete(): data.append( { "id": user.id, "type": "delete", "reverse": False, "reverse_name": None, "reverse_url": None } ) return jsonify( message=f"{len(data)} users deleted.", category="success", data=data, status=200 ) user = User.query.filter_by(id=user_id).first_or_404() if current_user.id == user.id: flash(_("You cannot delete yourself.", "danger")) return redirect(url_for("management.users")) user.delete() flash(_("User deleted."), "success") return redirect(url_for("management.users"))
class UnbanUser(MethodView): decorators = [ allows.requires( IsAtleastModerator, on_fail=FlashAndRedirect( message=_("You are not allowed to manage users"), level="danger", endpoint="management.overview" ) ) ] def post(self, user_id=None): if not Permission(CanBanUser, identity=current_user): flash( _("You do not have the permissions to unban this user."), "danger" ) return redirect(url_for("management.overview")) json = request.get_json() if json: ids = json["ids"] data = [] for user in User.query.filter(User.id.in_(ids)).all(): if user.unban(): data.append( { "id": user.id, "type": "unban", "reverse": "ban", "reverse_name": _("Ban"), "reverse_url": url_for("management.ban_user", user_id=user.id) } ) return jsonify( message="{} users unbanned.".format(len(data)), category="success", data=data, status=200 ) user = User.query.filter_by(id=user_id).first_or_404() if user.unban(): flash(_("User is now unbanned."), "success") else: flash(_("Could not unban user."), "danger") return redirect(url_for("management.banned_users"))
class PluginsView(MethodView): decorators = [ allows.requires(IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify plugins"), level="danger", endpoint="management.overview")) ] def get(self): plugins = PluginRegistry.query.all() return render_template("management/plugins.html", plugins=plugins)
class Forums(MethodView): decorators = [ allows.requires(IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify forums."), level="danger", endpoint="management.overview")) ] def get(self): categories = Category.query.order_by(Category.position.asc()).all() return render_template("management/forums.html", categories=categories)
class ManagementOverview(MethodView): decorators = [ allows.requires( IsAtleastModerator, on_fail=FlashAndRedirect( message=_("You are not allowed to access the management panel"), level="danger", endpoint="forum.index" ) ) ] def get(self): # user and group stats banned_users = User.query.filter( Group.banned == True, Group.id == User.primary_group_id ).count() if not current_app.config["REDIS_ENABLED"]: online_users = User.query.filter(User.lastseen >= time_diff() ).count() else: online_users = len(get_online_users()) unread_reports = Report.query.\ filter(Report.zapped == None).\ order_by(Report.id.desc()).\ count() python_version = "{}.{}.{}".format( sys.version_info[0], sys.version_info[1], sys.version_info[2] ) stats = { "current_app": current_app, "unread_reports": unread_reports, # stats stats "all_users": User.query.count(), "banned_users": banned_users, "online_users": online_users, "all_groups": Group.query.count(), "report_count": Report.query.count(), "topic_count": Topic.query.count(), "post_count": Post.query.count(), # components "python_version": python_version, "celery_version": celery_version, "flask_version": flask_version, "flaskbb_version": flaskbb_version, # plugins "plugins": PluginRegistry.query.all() } return render_template("management/overview.html", **stats)
class RawPost(MethodView): decorators = [ login_required, allows.requires( CanAccessForum(), on_fail=FlashAndRedirect( message=_("You are not allowed to access that forum"), level="warning", endpoint=lambda *a, **k: current_category.url)), ] def get(self, post_id): post = Post.query.filter_by(id=post_id).first_or_404() return format_quote(username=post.username, content=post.content)
class BannedUsers(MethodView): decorators = [ allows.requires( IsAtleastModerator, on_fail=FlashAndRedirect( message=_("You are not allowed to manage users"), level="danger", endpoint="management.overview" ) ) ] form = UserSearchForm def get(self): page = request.args.get('page', 1, type=int) search_form = self.form() users = User.query.filter( Group.banned == True, Group.id == User.primary_group_id ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False) return render_template( 'management/banned_users.html', users=users, search_form=search_form ) def post(self): page = request.args.get('page', 1, type=int) search_form = self.form() users = User.query.filter( Group.banned == True, Group.id == User.primary_group_id ).paginate(page, flaskbb_config['USERS_PER_PAGE'], False) if search_form.validate(): users = search_form.get_results().\ paginate(page, flaskbb_config['USERS_PER_PAGE'], False) return render_template( 'management/banned_users.html', users=users, search_form=search_form ) return render_template( 'management/banned_users.html', users=users, search_form=search_form )
class EditForum(MethodView): decorators = [ allows.requires( IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify forums."), level="danger", endpoint="management.overview" ) ) ] form = EditForumForm def get(self, forum_id): forum = Forum.query.filter_by(id=forum_id).first_or_404() form = self.form(forum) if forum.moderators: form.moderators.data = ','.join( [user.display_name for user in forum.moderators] ) else: form.moderators.data = None return render_template( 'management/forum_form.html', form=form, title=_('Edit Forum') ) def post(self, forum_id): forum = Forum.query.filter_by(id=forum_id).first_or_404() form = self.form(forum) if form.validate_on_submit(): form.save() flash(_('Forum updated.'), 'success') return redirect(url_for('management.edit_forum', forum_id=forum.id)) else: if forum.moderators: form.moderators.data = ','.join( [user.display_name for user in forum.moderators] ) else: form.moderators.data = None return render_template( 'management/forum_form.html', form=form, title=_('Edit Forum') )
class NewPost(MethodView): decorators = [ login_required, allows.requires(CanAccessForum(), CanPostReply, on_fail=FlashAndRedirect( message=_("You are not allowed to post a reply"), level="warning", endpoint=lambda *a, **k: url_for( "forum.view_topic", topic_id=k["topic_id"], ))), ] def get(self, topic_id, slug=None, post_id=None): topic = Topic.query.filter_by(id=topic_id).first_or_404() form = self.form() if post_id is not None: post = Post.query.filter_by(id=post_id).first_or_404() form.content.data = format_quote(post.username, post.content) return render_template("forum/new_post.html", topic=topic, form=form) def post(self, topic_id, slug=None, post_id=None): topic = Topic.query.filter_by(id=topic_id).first_or_404() form = self.form() # check if topic exists if post_id is not None: post = Post.query.filter_by(id=post_id).first_or_404() if form.validate_on_submit(): try: post = form.save(real(current_user), topic) return redirect(url_for("forum.view_post", post_id=post.id)) except StopNewPost as e: flash(e.reason, "danger") except BaseFlaskBBError as e: flash(e.reason, "warning") except Exception: flash(_("Unrecoverable error while submitting new post")) return render_template("forum/new_post.html", topic=topic, form=form) def form(self): current_app.pluggy.hook.flaskbb_form_new_post(form=ReplyForm) return ReplyForm()
class UntrackTopic(MethodView): decorators = [ login_required, allows.requires( CanAccessForum(), on_fail=FlashAndRedirect( message=_("You are not allowed to access that forum"), level="warning", endpoint=lambda *a, **k: current_category.url)), ] def post(self, topic_id, slug=None): topic = Topic.query.filter_by(id=topic_id).first_or_404() real(current_user).untrack_topic(topic) real(current_user).save() return redirect(topic.url)
class DeleteTopic(MethodView): decorators = [ login_required, allows.requires( CanDeleteTopic, on_fail=FlashAndRedirect( message=_("You are not allowed to delete this topic"), level="danger", # TODO(anr): consider the referrer -- for now, back to topic endpoint=lambda *a, **k: current_topic.url)), ] def post(self, topic_id, slug=None): topic = Topic.query.filter_by(id=topic_id).first_or_404() topic.delete() return redirect(url_for("forum.view_forum", forum_id=topic.forum_id))
class UninstallPlugin(MethodView): decorators = [ allows.requires(IsAdmin, on_fail=FlashAndRedirect( message=_("You are not allowed to modify plugins"), level="danger", endpoint="management.overview")) ] def post(self, name): validate_plugin(name) plugin = PluginRegistry.query.filter_by(name=name).first_or_404() PluginStore.query.filter_by(plugin_id=plugin.id).delete() db.session.commit() flash(_("Plugin has been uninstalled."), "success") return redirect(url_for("management.plugins"))
class Reports(MethodView): decorators = [ allows.requires(IsAtleastModerator, on_fail=FlashAndRedirect( message=_("You are not allowed to view reports."), level="danger", endpoint="management.overview")) ] def get(self): page = request.args.get("page", 1, type=int) reports = Report.query.\ order_by(Report.id.asc()).\ paginate(page, flaskbb_config['USERS_PER_PAGE'], False) return render_template("management/reports.html", reports=reports)
class NewPost(MethodView): decorators = [ login_required, allows.requires(CanAccessForum(), CanPostReply, on_fail=FlashAndRedirect( message=_("You are not allowed to post a reply"), level="warning", endpoint=lambda *a, **k: url_for( "forum.view_topic", topic_id=k["topic_id"], ))), ] def get(self, topic_id, slug=None, post_id=None): topic = Topic.query.filter_by(id=topic_id).first_or_404() form = self.form() if post_id is not None: post = Post.query.filter_by(id=post_id).first_or_404() form.content.data = format_quote(post.username, post.content) return render_template('forum/new_post.html', topic=topic, form=form) def post(self, topic_id, slug=None, post_id=None): topic = Topic.query.filter_by(id=topic_id).first_or_404() form = self.form() # check if topic exists if post_id is not None: post = Post.query.filter_by(id=post_id).first_or_404() if form.validate_on_submit(): if 'preview' in request.form: return render_template('forum/new_post.html', topic=topic, form=form, preview=form.content.data) else: post = form.save(real(current_user), topic) return redirect(url_for('forum.view_post', post_id=post.id)) return render_template('forum/new_post.html', topic=topic, form=form) def form(self): current_app.pluggy.hook.flaskbb_form_new_post(form=ReplyForm) return ReplyForm()