diff --git a/app/blueprints/users/settings.py b/app/blueprints/users/settings.py index d1cc5bf..c277f59 100644 --- a/app/blueprints/users/settings.py +++ b/app/blueprints/users/settings.py @@ -13,7 +13,7 @@ from . import bp def get_setting_tabs(user): - return [ + ret = [ { "id": "edit_profile", "title": gettext("Edit Profile"), @@ -36,6 +36,15 @@ def get_setting_tabs(user): }, ] + if current_user.rank.atLeast(UserRank.MODERATOR): + ret.append({ + "id": "modtools", + "title": gettext("Moderator Tools"), + "url": url_for("users.modtools", username=user.username) + }) + + return ret + class UserProfileForm(FlaskForm): display_name = StringField(lazy_gettext("Display Name"), [Optional(), Length(1, 20)], filters=[lambda x: nonEmptyOrNone(x)]) @@ -194,65 +203,14 @@ def email_notifications(username=None): tabs=get_setting_tabs(user), current_tab="notifications") -class UserAccountForm(FlaskForm): - username = StringField(lazy_gettext("Username"), [Optional(), Length(1, 50)]) - display_name = StringField(lazy_gettext("Display name"), [Optional(), Length(2, 100)]) - forums_username = StringField(lazy_gettext("Forums Username"), [Optional(), Length(2, 50)]) - github_username = StringField(lazy_gettext("GitHub Username"), [Optional(), Length(2, 50)]) - rank = SelectField(lazy_gettext("Rank"), [Optional()], choices=UserRank.choices(), coerce=UserRank.coerce, - default=UserRank.NEW_MEMBER) - submit = SubmitField(lazy_gettext("Save")) - - -@bp.route("/users//settings/account/", methods=["GET", "POST"]) +@bp.route("/users//settings/account/") @login_required def account(username): user : User = User.query.filter_by(username=username).first() if not user: abort(404) - if not user.can_see_edit_profile(current_user): - flash(gettext("Permission denied"), "danger") - return redirect(url_for("users.profile", username=username)) - - can_edit_account_settings = user.checkPerm(current_user, Permission.CHANGE_USERNAMES) or \ - user.checkPerm(current_user, Permission.CHANGE_RANK) - form = UserAccountForm(obj=user) if can_edit_account_settings else None - if form and form.validate_on_submit(): - severity = AuditSeverity.NORMAL if current_user == user else AuditSeverity.MODERATION - addAuditLog(severity, current_user, "Edited {}'s profile".format(user.display_name), - url_for("users.profile", username=username)) - - # Copy form fields to user_profile fields - if user.checkPerm(current_user, Permission.CHANGE_USERNAMES): - if user.username != form.username.data: - for package in user.packages: - alias = PackageAlias(user.username, package.name) - package.aliases.append(alias) - db.session.add(alias) - - user.username = form.username.data - - user.display_name = form.display_name.data - user.forums_username = nonEmptyOrNone(form.forums_username.data) - user.github_username = nonEmptyOrNone(form.github_username.data) - - if user.checkPerm(current_user, Permission.CHANGE_RANK): - newRank = form["rank"].data - if current_user.rank.atLeast(newRank): - if newRank != user.rank: - user.rank = form["rank"].data - msg = "Set rank of {} to {}".format(user.display_name, user.rank.getTitle()) - addAuditLog(AuditSeverity.MODERATION, current_user, msg, - url_for("users.profile", username=username)) - else: - flash(gettext("Can't promote a user to a rank higher than yourself!"), "danger") - - db.session.commit() - - return redirect(url_for("users.account", username=username)) - - return render_template("users/account.html", user=user, form=form, tabs=get_setting_tabs(user), current_tab="account") + return render_template("users/account.html", user=user, tabs=get_setting_tabs(user), current_tab="account") @bp.route("/users//delete/", methods=["GET", "POST"]) @@ -299,3 +257,112 @@ def delete(username): logout_user() return redirect(url_for("homepage.home")) + + +class ModToolsForm(FlaskForm): + username = StringField(lazy_gettext("Username"), [Optional(), Length(1, 50)]) + display_name = StringField(lazy_gettext("Display name"), [Optional(), Length(2, 100)]) + forums_username = StringField(lazy_gettext("Forums Username"), [Optional(), Length(2, 50)]) + github_username = StringField(lazy_gettext("GitHub Username"), [Optional(), Length(2, 50)]) + rank = SelectField(lazy_gettext("Rank"), [Optional()], choices=UserRank.choices(), coerce=UserRank.coerce, + default=UserRank.NEW_MEMBER) + submit = SubmitField(lazy_gettext("Save")) + + +@bp.route("/users//modtools/", methods=["GET", "POST"]) +@rank_required(UserRank.MODERATOR) +def modtools(username): + user: User = User.query.filter_by(username=username).first() + if not user: + abort(404) + + if not user.checkPerm(current_user, Permission.CHANGE_EMAIL): + abort(403) + + form = ModToolsForm(obj=user) + if form.validate_on_submit(): + severity = AuditSeverity.NORMAL if current_user == user else AuditSeverity.MODERATION + addAuditLog(severity, current_user, "Edited {}'s account".format(user.display_name), + url_for("users.profile", username=username)) + + # Copy form fields to user_profile fields + if user.checkPerm(current_user, Permission.CHANGE_USERNAMES): + if user.username != form.username.data: + for package in user.packages: + alias = PackageAlias(user.username, package.name) + package.aliases.append(alias) + db.session.add(alias) + + user.username = form.username.data + + user.display_name = form.display_name.data + user.forums_username = nonEmptyOrNone(form.forums_username.data) + user.github_username = nonEmptyOrNone(form.github_username.data) + + if user.checkPerm(current_user, Permission.CHANGE_RANK): + newRank = form["rank"].data + if current_user.rank.atLeast(newRank): + if newRank != user.rank: + user.rank = form["rank"].data + msg = "Set rank of {} to {}".format(user.display_name, user.rank.getTitle()) + addAuditLog(AuditSeverity.MODERATION, current_user, msg, + url_for("users.profile", username=username)) + else: + flash(gettext("Can't promote a user to a rank higher than yourself!"), "danger") + + db.session.commit() + + return redirect(url_for("users.modtools", username=username)) + + return render_template("users/modtools.html", user=user, form=form, tabs=get_setting_tabs(user), current_tab="modtools") + + +@bp.route("/users//modtools/set-email/", methods=["POST"]) +@rank_required(UserRank.MODERATOR) +def modtools_set_email(username): + user: User = User.query.filter_by(username=username).first() + if not user: + abort(404) + + if not user.checkPerm(current_user, Permission.CHANGE_EMAIL): + abort(403) + + user.email = request.form["email"] + user.is_active = False + + token = randomString(32) + addAuditLog(AuditSeverity.MODERATION, current_user, f"Set email and sent a password reset on {user.username}", + url_for("users.profile", username=user.username), None) + + ver = UserEmailVerification() + ver.user = user + ver.token = token + ver.email = user.email + ver.is_password_reset = True + db.session.add(ver) + db.session.commit() + + send_verify_email.delay(user.email, token) + + flash(f"Set email and sent a password reset on {user.username}", "success") + return redirect(url_for("users.modtools", username=username)) + + +@bp.route("/users//modtools/ban/", methods=["POST"]) +@rank_required(UserRank.MODERATOR) +def modtools_ban(username): + user: User = User.query.filter_by(username=username).first() + if not user: + abort(404) + + if not user.checkPerm(current_user, Permission.CHANGE_RANK): + abort(403) + + user.rank = UserRank.BANNED + + addAuditLog(AuditSeverity.MODERATION, current_user, f"Banned {user.username}", + url_for("users.profile", username=user.username), None) + db.session.commit() + + flash(f"Banned {user.username}", "success") + return redirect(url_for("users.modtools", username=username)) \ No newline at end of file diff --git a/app/models/users.py b/app/models/users.py index 0fa917d..a1a1e9b 100644 --- a/app/models/users.py +++ b/app/models/users.py @@ -216,10 +216,12 @@ class User(db.Model, UserMixin): # Members can edit their own packages, and editors can edit any packages if perm == Permission.CHANGE_AUTHOR: return user.rank.atLeast(UserRank.EDITOR) - elif perm == Permission.CHANGE_RANK or perm == Permission.CHANGE_USERNAMES: + elif perm == Permission.CHANGE_USERNAMES: return user.rank.atLeast(UserRank.MODERATOR) + elif perm == Permission.CHANGE_RANK: + return user.rank.atLeast(UserRank.MODERATOR) and not self.rank.atLeast(user.rank) elif perm == Permission.CHANGE_EMAIL or perm == Permission.CHANGE_PROFILE_URLS: - return user == self or user.rank.atLeast(UserRank.ADMIN) + return user == self or (user.rank.atLeast(UserRank.MODERATOR) and not self.rank.atLeast(user.rank)) elif perm == Permission.CHANGE_DISPLAY_NAME: return user.rank.atLeast(UserRank.MEMBER if user == self else UserRank.MODERATOR) elif perm == Permission.CREATE_TOKEN: diff --git a/app/templates/users/account.html b/app/templates/users/account.html index 46728ff..6c7a978 100644 --- a/app/templates/users/account.html +++ b/app/templates/users/account.html @@ -11,33 +11,6 @@ {% block pane %}

{{ _("Account and Security") }}

-{% if form %} -

- {{ _("Edit Account") }} - -

- - {% from "macros/forms.html" import render_field, render_field_prefix, render_submit_field %} -
- {{ form.hidden_tag() }} - - {% if user.checkPerm(current_user, "CHANGE_USERNAMES") %} - {{ render_field(form.username, tabindex=230) }} - {{ render_field(form.display_name, tabindex=230) }} - {{ render_field(form.forums_username, tabindex=230) }} - {{ render_field_prefix(form.github_username, tabindex=230) }} - {% endif %} - - {% if user.checkPerm(current_user, "CHANGE_RANK") %} - {{ render_field(form.rank, tabindex=250) }} - {% endif %} - -

- {{ render_submit_field(form.submit, tabindex=280) }} -

-
-{% endif %} -

{{ _("Password") }}

{% if user == current_user %} {% if user.password %} diff --git a/app/templates/users/modtools.html b/app/templates/users/modtools.html new file mode 100644 index 0000000..846dff1 --- /dev/null +++ b/app/templates/users/modtools.html @@ -0,0 +1,66 @@ +{% extends "users/settings_base.html" %} + +{% block title %} + {{ _("Moderator Tools") }} +{% endblock %} + +{% block pane %} +

{{ _("Moderator Tools") }}

+ +

+ {{ _("Edit Account") }} +

+ +{% from "macros/forms.html" import render_field, render_field_prefix, render_submit_field %} +
+ {{ form.hidden_tag() }} + + {% if user.checkPerm(current_user, "CHANGE_USERNAMES") %} + {{ render_field(form.username, tabindex=230) }} + {{ render_field(form.display_name, tabindex=230) }} + {{ render_field(form.forums_username, tabindex=230) }} + {{ render_field_prefix(form.github_username, tabindex=230) }} + {% endif %} + + {% if user.checkPerm(current_user, "CHANGE_RANK") %} + {{ render_field(form.rank, tabindex=250) }} + {% endif %} + +

+ {{ render_submit_field(form.submit, tabindex=280) }} +

+
+ + +

{{ _("Password") }}

+{% if user.password %} +

{{ _("Has password") }}

+{% else %} +

{{ _("Doesn't have password") }}

+{% endif %} + +{% if not user.rank.atLeast(current_user.rank) %} +

{{ _("Ban") }}

+ {% if user.rank.name == "BANNED" %} +

+ Banned. +

+ {% else %} +
+ + +
+ {% endif %} + +

{{ _("Change Email and Send Password Reset") }}

+
+ +
+ + +
+ +
+{% endif %} + +{% endblock %} diff --git a/app/templates/users/profile.html b/app/templates/users/profile.html index 37cb424..4872aac 100644 --- a/app/templates/users/profile.html +++ b/app/templates/users/profile.html @@ -28,11 +28,19 @@ {{ _("Report") }} - {% if current_user.is_authenticated and current_user.rank.atLeast(current_user.rank.MODERATOR) and user.email %} - - - {{ _("Send Email") }} - + {% if current_user.is_authenticated and current_user.rank.atLeast(current_user.rank.MODERATOR) %} + {% if not user.rank.atLeast(current_user.rank) %} + + + {{ _("Moderator Tools") }} + + {% endif %} + {% if user.email %} + + + {{ _("Send Email") }} + + {% endif %} {% endif %}