From cd1041c3a413b9b3ba4c763308b5fd77a53d7c3c Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 27 Dec 2021 02:27:48 +0300 Subject: [PATCH 1/5] API: optionally restrict moderators from accessing sensitive data --- CHANGELOG.md | 3 +- config/config.exs | 3 +- config/description.exs | 5 +++ .../web/plugs/ensure_staff_privileged.ex | 31 +++++++++++++++++++ lib/pleroma/web/router.ex | 31 +++++++++++++------ 5 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 lib/pleroma/web/plugs/ensure_staff_privileged.ex diff --git a/CHANGELOG.md b/CHANGELOG.md index ee9e04568..79fe674a9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object +- AdminAPI: allow moderators to manage reports, users, invites, and custom emojis +- AdminAPI: restrict moderators to access sensitive data: change user credentials, get password reset token, read private statuses and chats, etc ### Fixed - Subscription(Bell) Notifications: Don't create from Pipeline Ingested replies @@ -67,7 +69,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Attachment dimensions and blurhashes are federated when available. - Mastodon API: support `poll` notification. - Pinned posts federation -- AdminAPI: allow moderators to manage reports, users, invites, and custom emojis ### Fixed - Don't crash so hard when email settings are invalid. diff --git a/config/config.exs b/config/config.exs index 23c41eddd..ec242cadc 100644 --- a/config/config.exs +++ b/config/config.exs @@ -255,7 +255,8 @@ ], show_reactions: true, password_reset_token_validity: 60 * 60 * 24, - profile_directory: true + profile_directory: true, + privileged_staff: false config :pleroma, :welcome, direct_message: [ diff --git a/config/description.exs b/config/description.exs index 517077acf..a8fbd4d73 100644 --- a/config/description.exs +++ b/config/description.exs @@ -941,6 +941,11 @@ key: :profile_directory, type: :boolean, description: "Enable profile directory." + }, + %{ + key: :privileged_staff, + type: :boolean, + description: "Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)" } ] }, diff --git a/lib/pleroma/web/plugs/ensure_staff_privileged.ex b/lib/pleroma/web/plugs/ensure_staff_privileged.ex new file mode 100644 index 000000000..b15ddfc56 --- /dev/null +++ b/lib/pleroma/web/plugs/ensure_staff_privileged.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug do + @moduledoc """ + Ensures if staff are privileged enough to do certain tasks + """ + + import Pleroma.Web.TranslationHelpers + import Plug.Conn + + alias Pleroma.User + alias Pleroma.Config + + def init(options) do + options + end + + def call(%{assigns: %{user: %User{is_admin: true}}} = conn, _), do: conn + + def call(conn, _) do + if Config.get!([:instance, :privileged_staff]) do + conn + else + conn + |> render_error(:forbidden, "User is not an admin.") + |> halt() + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index b2ca09784..7ba72994b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -101,6 +101,10 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.IdempotencyPlug) end + pipeline :require_privileged_staff do + plug(Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug) + end + pipeline :require_admin do plug(Pleroma.Web.Plugs.UserIsAdminPlug) end @@ -228,6 +232,24 @@ defmodule Pleroma.Web.Router do post("/backups", AdminAPIController, :create_backup) end + # AdminAPI: admins and mods (staff) can perform these actions (if enabled by config) + scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do + pipe_through([:admin_api, :require_privileged_staff]) + + delete("/users", UserController, :delete) + + get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) + patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) + + get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) + get("/users/:nickname/chats", AdminAPIController, :list_user_chats) + + get("/statuses", StatusController, :index) + + get("/chats/:id", ChatController, :show) + get("/chats/:id/messages", ChatController, :messages) + end + # AdminAPI: admins and mods (staff) can perform these actions scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do pipe_through(:admin_api) @@ -240,22 +262,16 @@ defmodule Pleroma.Web.Router do patch("/users/deactivate", UserController, :deactivate) patch("/users/approve", UserController, :approve) - delete("/users", UserController, :delete) - post("/users/invite_token", InviteController, :create) get("/users/invites", InviteController, :index) post("/users/revoke_invite", InviteController, :revoke) post("/users/email_invite", InviteController, :email) - get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) patch("/users/force_password_reset", AdminAPIController, :force_password_reset) get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials) - patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) get("/users", UserController, :index) get("/users/:nickname", UserController, :show) - get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) - get("/users/:nickname/chats", AdminAPIController, :list_user_chats) get("/instances/:instance/statuses", InstanceController, :list_statuses) delete("/instances/:instance", InstanceController, :delete) @@ -269,15 +285,12 @@ defmodule Pleroma.Web.Router do get("/statuses/:id", StatusController, :show) put("/statuses/:id", StatusController, :update) delete("/statuses/:id", StatusController, :delete) - get("/statuses", StatusController, :index) get("/moderation_log", AdminAPIController, :list_log) post("/reload_emoji", AdminAPIController, :reload_emoji) get("/stats", AdminAPIController, :stats) - get("/chats/:id", ChatController, :show) - get("/chats/:id/messages", ChatController, :messages) delete("/chats/:id/messages/:message_id", ChatController, :delete_message) end From 1c223331fc7276a7e5946b6dbd5d2b713cd6c1e8 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 27 Dec 2021 02:28:09 +0300 Subject: [PATCH 2/5] API: show info about privileged staff in instance metadata --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 ++- lib/pleroma/web/nodeinfo/nodeinfo.ex | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 7072d5d61..8e657ee0f 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -45,7 +45,8 @@ def render("show.json", _) do features: features(), federation: federation(), fields_limits: fields_limits(), - post_formats: Config.get([:instance, :allowed_post_formats]) + post_formats: Config.get([:instance, :allowed_post_formats]), + privileged_staff: Config.get([:instance, :privileged_staff]) }, stats: %{mau: Pleroma.User.active_user_count()}, vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index 3781781c8..80a2ce676 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -69,7 +69,8 @@ def get_nodeinfo("2.0") do mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), features: features, restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), - skipThreadContainment: Config.get([:instance, :skip_thread_containment], false) + skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), + privilegedStaff: Config.get([:instance, :privileged_staff]) } } end From f66675f349a6e6b8111280e1abd23871688f6179 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 27 Dec 2021 02:57:54 +0300 Subject: [PATCH 3/5] API: fix duplicate :get_password_token route --- lib/pleroma/web/router.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 7ba72994b..5473cd93d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -199,7 +199,6 @@ defmodule Pleroma.Web.Router do post("/relay", RelayController, :follow) delete("/relay", RelayController, :unfollow) - get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) patch("/users/force_password_reset", AdminAPIController, :force_password_reset) get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials) patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) From f02715c4b2bfe5b1f055e44d8fece2047d85b611 Mon Sep 17 00:00:00 2001 From: Alibek Omarov Date: Mon, 27 Dec 2021 03:12:32 +0300 Subject: [PATCH 4/5] Fix lint errors --- config/description.exs | 3 ++- ...ure_staff_privileged.ex => ensure_staff_privileged_plug.ex} | 2 +- lib/pleroma/web/router.ex | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) rename lib/pleroma/web/plugs/{ensure_staff_privileged.ex => ensure_staff_privileged_plug.ex} (100%) diff --git a/config/description.exs b/config/description.exs index a8fbd4d73..ea3f34abe 100644 --- a/config/description.exs +++ b/config/description.exs @@ -945,7 +945,8 @@ %{ key: :privileged_staff, type: :boolean, - description: "Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)" + description: + "Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)" } ] }, diff --git a/lib/pleroma/web/plugs/ensure_staff_privileged.ex b/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex similarity index 100% rename from lib/pleroma/web/plugs/ensure_staff_privileged.ex rename to lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex index b15ddfc56..fe0a11dec 100644 --- a/lib/pleroma/web/plugs/ensure_staff_privileged.ex +++ b/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex @@ -10,8 +10,8 @@ defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug do import Pleroma.Web.TranslationHelpers import Plug.Conn - alias Pleroma.User alias Pleroma.Config + alias Pleroma.User def init(options) do options diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 5473cd93d..02ca8d70a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -238,7 +238,7 @@ defmodule Pleroma.Web.Router do delete("/users", UserController, :delete) get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset) - patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) + patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials) get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses) get("/users/:nickname/chats", AdminAPIController, :list_user_chats) From 479fc5fff8355e552c5d2297d83f4ca7456d4f03 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Mon, 27 Dec 2021 10:39:59 -0600 Subject: [PATCH 5/5] EnsureStaffPrivilegedPlug: add tests --- .../ensure_staff_privileged_plug_test.exs | 60 +++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs diff --git a/test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs b/test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs new file mode 100644 index 000000000..74f4ae504 --- /dev/null +++ b/test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlugTest do + use Pleroma.Web.ConnCase, async: true + + alias Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug + import Pleroma.Factory + + test "accepts a user that is an admin" do + user = insert(:user, is_admin: true) + + conn = assign(build_conn(), :user, user) + + ret_conn = EnsureStaffPrivilegedPlug.call(conn, %{}) + + assert conn == ret_conn + end + + test "accepts a user that is a moderator when :privileged_staff is enabled" do + clear_config([:instance, :privileged_staff], true) + user = insert(:user, is_moderator: true) + + conn = assign(build_conn(), :user, user) + + ret_conn = EnsureStaffPrivilegedPlug.call(conn, %{}) + + assert conn == ret_conn + end + + test "denies a user that is a moderator when :privileged_staff is disabled" do + clear_config([:instance, :privileged_staff], false) + user = insert(:user, is_moderator: true) + + conn = + build_conn() + |> assign(:user, user) + |> EnsureStaffPrivilegedPlug.call(%{}) + + assert conn.status == 403 + end + + test "denies a user that isn't a staff member" do + user = insert(:user) + + conn = + build_conn() + |> assign(:user, user) + |> EnsureStaffPrivilegedPlug.call(%{}) + + assert conn.status == 403 + end + + test "denies when a user isn't set" do + conn = EnsureStaffPrivilegedPlug.call(build_conn(), %{}) + + assert conn.status == 403 + end +end