diff --git a/config/config.exs b/config/config.exs
index 6a5acda09..2d501e577 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -257,6 +257,8 @@ config :pleroma, :instance,
password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
privileged_staff: false,
+ admin_privileges: [],
+ moderator_privileges: [],
max_endorsed_users: 20,
birthday_required: false,
birthday_min_age: 0,
diff --git a/config/description.exs b/config/description.exs
index 704af8f68..b73b92c46 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -966,6 +966,18 @@ config :pleroma, :config_description, [
description:
"Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
},
+ %{
+ key: :admin_privileges,
+ type: {:list, :atom},
+ suggestions: [],
+ description: "What extra priviledges to allow admins (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
+ },
+ %{
+ key: :moderator_privileges,
+ type: {:list, :atom},
+ suggestions: [],
+ description: "What extra priviledges to allow moderators (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
+ },
%{
key: :birthday_required,
type: :boolean,
diff --git a/lib/pleroma/web/plugs/ensure_privileged_plug.ex b/lib/pleroma/web/plugs/ensure_privileged_plug.ex
new file mode 100644
index 000000000..be09f3d81
--- /dev/null
+++ b/lib/pleroma/web/plugs/ensure_privileged_plug.ex
@@ -0,0 +1,44 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsurePrivilegedPlug do
+ @moduledoc """
+ Ensures staff are privileged enough to do certain tasks.
+ """
+ import Pleroma.Web.TranslationHelpers
+ import Plug.Conn
+
+ alias Pleroma.Config
+ alias Pleroma.User
+
+ def init(options) do
+ options
+ end
+
+ def call(%{assigns: %{user: %User{is_admin: false, is_moderator: false}}} = conn, _) do
+ conn
+ |> render_error(:forbidden, "User isn't privileged.")
+ |> halt()
+ end
+
+ def call(
+ %{assigns: %{user: %User{is_admin: is_admin, is_moderator: is_moderator}}} = conn,
+ priviledge
+ ) do
+ if (is_admin and priviledge in Config.get([:instance, :admin_privileges])) or
+ (is_moderator and priviledge in Config.get([:instance, :moderator_privileges])) do
+ conn
+ else
+ conn
+ |> render_error(:forbidden, "User isn't privileged.")
+ |> halt()
+ end
+ end
+
+ def call(conn, _) do
+ conn
+ |> render_error(:forbidden, "User isn't privileged.")
+ |> halt()
+ end
+end
diff --git a/test/pleroma/web/plugs/ensure_privileged_plug_test.exs b/test/pleroma/web/plugs/ensure_privileged_plug_test.exs
new file mode 100644
index 000000000..423413946
--- /dev/null
+++ b/test/pleroma/web/plugs/ensure_privileged_plug_test.exs
@@ -0,0 +1,96 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.Plugs.EnsurePrivilegedPlugTest do
+ use Pleroma.Web.ConnCase, async: true
+
+ alias Pleroma.Web.Plugs.EnsurePrivilegedPlug
+ import Pleroma.Factory
+
+ test "denies a user that isn't moderator or admin" do
+ clear_config([:instance, :admin_privileges], [])
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> EnsurePrivilegedPlug.call(:cofe)
+
+ assert conn.status == 403
+ end
+
+ test "accepts an admin that is privileged" do
+ clear_config([:instance, :admin_privileges], [:cofe])
+ user = insert(:user, is_admin: true)
+ conn = assign(build_conn(), :user, user)
+
+ ret_conn = EnsurePrivilegedPlug.call(conn, :cofe)
+
+ assert conn == ret_conn
+ end
+
+ test "denies an admin that isn't privileged" do
+ clear_config([:instance, :admin_privileges], [:suya])
+ user = insert(:user, is_admin: true)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> EnsurePrivilegedPlug.call(:cofe)
+
+ assert conn.status == 403
+ end
+
+ test "accepts a moderator that is privileged" do
+ clear_config([:instance, :moderator_privileges], [:cofe])
+ user = insert(:user, is_moderator: true)
+ conn = assign(build_conn(), :user, user)
+
+ ret_conn = EnsurePrivilegedPlug.call(conn, :cofe)
+
+ assert conn == ret_conn
+ end
+
+ test "denies a moderator that isn't privileged" do
+ clear_config([:instance, :moderator_privileges], [:suya])
+ user = insert(:user, is_moderator: true)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> EnsurePrivilegedPlug.call(:cofe)
+
+ assert conn.status == 403
+ end
+
+ test "accepts for a priviledged role even if other role isn't priviledged" do
+ clear_config([:instance, :admin_privileges], [:cofe])
+ clear_config([:instance, :moderator_privileges], [])
+ user = insert(:user, is_admin: true, is_moderator: true)
+ conn = assign(build_conn(), :user, user)
+
+ ret_conn = EnsurePrivilegedPlug.call(conn, :cofe)
+
+ # priviledged through admin role
+ assert conn == ret_conn
+
+ clear_config([:instance, :admin_privileges], [])
+ clear_config([:instance, :moderator_privileges], [:cofe])
+ user = insert(:user, is_admin: true, is_moderator: true)
+ conn = assign(build_conn(), :user, user)
+
+ ret_conn = EnsurePrivilegedPlug.call(conn, :cofe)
+
+ # priviledged through moderator role
+ assert conn == ret_conn
+ end
+
+ test "denies when no user is set" do
+ conn =
+ build_conn()
+ |> EnsurePrivilegedPlug.call(:cofe)
+
+ assert conn.status == 403
+ end
+end