diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7d1ae3160..7d0243e36 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,8 +24,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- `activeMonth` and `activeHalfyear` fields in NodeInfo usage.users object
- Experimental support for Finch. Put `config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch}` in your secrets file to use it. Reverse Proxy will still use Hackney.
- `ForceMentionsInPostContent` MRF policy
-- 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
- PleromaAPI: Add remote follow API endpoint at `POST /api/v1/pleroma/remote_interaction`
- MastoAPI: Add `GET /api/v1/accounts/lookup`
- MastoAPI: Profile Directory support
@@ -37,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Configuration: Add `birthday_required` and `birthday_min_age` settings to provide a way to require users to enter their birth date.
- PleromaAPI: Add `GET /api/v1/pleroma/birthdays` API endpoint
- Make backend-rendered pages translatable. This includes emails. Pages returned as a HTTP response are translated using the language specified in the `userLanguage` cookie, or the `Accept-Language` header. Emails are translated using the `language` field when registering. This language can be changed by `PATCH /api/v1/accounts/update_credentials` with the `language` field.
+- Add fine grained options to provide privileges to moderators and admins (e.g. delete messages, manage reports...)
- Uploadfilter `Pleroma.Upload.Filter.Exiftool.ReadDescription` returns description values to the FE so they can pre fill the image description field
- Added move account API
- Enable remote users to interact with posts
diff --git a/config/config.exs b/config/config.exs
index 821166fd9..e41ec2f91 100644
--- a/config/config.exs
+++ b/config/config.exs
@@ -253,7 +253,23 @@
show_reactions: true,
password_reset_token_validity: 60 * 60 * 24,
profile_directory: true,
- privileged_staff: false,
+ admin_privileges: [
+ :users_read,
+ :users_manage_invites,
+ :users_manage_activation_state,
+ :users_manage_tags,
+ :users_manage_credentials,
+ :users_delete,
+ :messages_read,
+ :messages_delete,
+ :instances_delete,
+ :reports_manage_reports,
+ :moderation_log_read,
+ :announcements_manage_announcements,
+ :emoji_manage_emoji,
+ :statistics_read
+ ],
+ moderator_privileges: [:messages_delete, :reports_manage_reports],
max_endorsed_users: 20,
birthday_required: false,
birthday_min_age: 0,
diff --git a/config/description.exs b/config/description.exs
index 3848cb449..bf4734426 100644
--- a/config/description.exs
+++ b/config/description.exs
@@ -998,10 +998,48 @@
description: "Enable profile directory."
},
%{
- key: :privileged_staff,
- type: :boolean,
+ key: :admin_privileges,
+ type: {:list, :atom},
+ suggestions: [
+ :users_read,
+ :users_manage_invites,
+ :users_manage_activation_state,
+ :users_manage_tags,
+ :users_manage_credentials,
+ :users_delete,
+ :messages_read,
+ :messages_delete,
+ :instances_delete,
+ :reports_manage_reports,
+ :moderation_log_read,
+ :announcements_manage_announcements,
+ :emoji_manage_emoji,
+ :statistics_read
+ ],
description:
- "Let moderators access sensitive data (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
+ "What extra privileges 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: [
+ :users_read,
+ :users_manage_invites,
+ :users_manage_activation_state,
+ :users_manage_tags,
+ :users_manage_credentials,
+ :users_delete,
+ :messages_read,
+ :messages_delete,
+ :instances_delete,
+ :reports_manage_reports,
+ :moderation_log_read,
+ :announcements_manage_announcements,
+ :emoji_manage_emoji,
+ :statistics_read
+ ],
+ description:
+ "What extra privileges to allow moderators (e.g. updating user credentials, get password reset token, delete users, index and read private statuses and chats)"
},
%{
key: :birthday_required,
diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md
index 314fdbbab..bbdf30a0f 100644
--- a/docs/configuration/cheatsheet.md
+++ b/docs/configuration/cheatsheet.md
@@ -66,6 +66,36 @@ To add configuration to your config file, you can copy it from the base config.
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
* `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`).
* `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day).
+* `admin_privileges`: A list of privileges an admin has (e.g. delete messages, manage reports...)
+ * Possible values are:
+ * `:users_read`
+ * Allows admins to fetch users through the admin API.
+ * `:users_manage_invites`
+ * Allows admins to manage invites. This includes sending, resending, revoking and approving invites.
+ * `:users_manage_activation_state`
+ * Allows admins to activate and deactivate accounts. This also allows them to see deactivated users through the Mastodon API.
+ * `:users_manage_tags`
+ * Allows admins to set and remove tags for users. This can be useful in combination with MRF policies, such as `Pleroma.Web.ActivityPub.MRF.TagPolicy`.
+ * `:users_manage_credentials`
+ * Allows admins to trigger a password reset and set new credentials for an user.
+ * `:users_delete`
+ * Allows admins to delete accounts. Note that deleting an account is actually deactivating it and removing all data like posts, profile information, etc.
+ * `:messages_read`
+ * Allows admins to read messages through the admin API, including non-public posts and chats.
+ * `:messages_delete`
+ * Allows admins to delete messages from other users.
+ * `:instances_delete,`
+ * Allows admins to remove a whole remote instance from your instance. This will delete all users and messages from that remote instance.
+ * `:reports_manage_reports`
+ * Allows admins to see and manage reports.
+ * `:moderation_log_read,`
+ * Allows admins to read the entries in the moderation log.
+ * `:emoji_manage_emoji`
+ * Allows admins to manage custom emoji on the instance.
+ * `:statistics_read,`
+ * Allows admins to see some simple statistics about the instance.
+* `moderator_privileges`: A list of privileges a moderator has (e.g. delete messages, manage reports...)
+ * Possible values are the same as for `admin_privileges`
## :database
* `improved_hashtag_timeline`: Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes).
diff --git a/docs/installation/migrating_from_source_otp_en.md b/docs/installation/migrating_from_source_otp_en.md
index e4a01d8db..f6f23400a 100644
--- a/docs/installation/migrating_from_source_otp_en.md
+++ b/docs/installation/migrating_from_source_otp_en.md
@@ -5,7 +5,7 @@
In this guide we cover how you can migrate from a from source installation to one using OTP releases.
## Pre-requisites
-You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
+You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo su`/`su`.
The system needs to have `curl` and `unzip` installed for downloading and unpacking release builds.
diff --git a/docs/installation/otp_en.md b/docs/installation/otp_en.md
index 0861a8157..8c02201e6 100644
--- a/docs/installation/otp_en.md
+++ b/docs/installation/otp_en.md
@@ -8,7 +8,7 @@ This guide covers a installation using an OTP release. To install Pleroma from s
* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and `x86_64`, `aarch64` or `armv7l` CPU, you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below
* A (sub)domain pointed to the machine
-You will be running commands as root. If you aren't root already, please elevate your priviledges by executing `sudo su`/`su`.
+You will be running commands as root. If you aren't root already, please elevate your privileges by executing `sudo su`/`su`.
While in theory OTP releases are possbile to install on any compatible machine, for the sake of simplicity this guide focuses only on Debian/Ubuntu and Alpine.
diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex
index c2d4d86a3..aba6096bc 100644
--- a/lib/pleroma/notification.ex
+++ b/lib/pleroma/notification.ex
@@ -338,14 +338,6 @@ def destroy_multiple(%{id: user_id} = _user, ids) do
|> Repo.delete_all()
end
- def destroy_multiple_from_types(%{id: user_id}, types) do
- from(n in Notification,
- where: n.user_id == ^user_id,
- where: n.type in ^types
- )
- |> Repo.delete_all()
- end
-
def dismiss(%Pleroma.Activity{} = activity) do
Notification
|> where([n], n.activity_id == ^activity.id)
@@ -559,7 +551,9 @@ def get_potential_receiver_ap_ids(%{data: %{"type" => "Follow", "object" => obje
end
def get_potential_receiver_ap_ids(%{data: %{"type" => "Flag", "actor" => actor}}) do
- (User.all_superusers() |> Enum.map(fn user -> user.ap_id end)) -- [actor]
+ (User.all_users_with_privilege(:reports_manage_reports)
+ |> Enum.map(fn user -> user.ap_id end)) --
+ [actor]
end
# Update activity: notify all who repeated this
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 4f91b3ffc..f6e30555c 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -326,7 +326,7 @@ def visible_for(%User{} = user, nil) do
end
def visible_for(%User{} = user, for_user) do
- if superuser?(for_user) do
+ if privileged?(for_user, :users_manage_activation_state) do
:visible
else
visible_account_status(user)
@@ -353,10 +353,45 @@ defp visible_account_status(user) do
end
end
- @spec superuser?(User.t()) :: boolean()
- def superuser?(%User{local: true, is_admin: true}), do: true
- def superuser?(%User{local: true, is_moderator: true}), do: true
- def superuser?(_), do: false
+ @spec privileged?(User.t(), atom()) :: boolean()
+ def privileged?(%User{is_admin: false, is_moderator: false}, _), do: false
+
+ def privileged?(
+ %User{local: true, is_admin: is_admin, is_moderator: is_moderator},
+ privilege_tag
+ ),
+ do:
+ privileged_for?(privilege_tag, is_admin, :admin_privileges) or
+ privileged_for?(privilege_tag, is_moderator, :moderator_privileges)
+
+ def privileged?(_, _), do: false
+
+ defp privileged_for?(privilege_tag, true, config_role_key),
+ do: privilege_tag in Config.get([:instance, config_role_key])
+
+ defp privileged_for?(_, _, _), do: false
+
+ @spec privileges(User.t()) :: [atom()]
+ def privileges(%User{local: false}) do
+ []
+ end
+
+ def privileges(%User{is_moderator: false, is_admin: false}) do
+ []
+ end
+
+ def privileges(%User{local: true, is_moderator: true, is_admin: true}) do
+ (Config.get([:instance, :moderator_privileges]) ++ Config.get([:instance, :admin_privileges]))
+ |> Enum.uniq()
+ end
+
+ def privileges(%User{local: true, is_moderator: true, is_admin: false}) do
+ Config.get([:instance, :moderator_privileges])
+ end
+
+ def privileges(%User{local: true, is_moderator: false, is_admin: true}) do
+ Config.get([:instance, :admin_privileges])
+ end
@spec invisible?(User.t()) :: boolean()
def invisible?(%User{invisible: true}), do: true
@@ -1167,24 +1202,10 @@ def update_and_set_cache(struct, params) do
|> update_and_set_cache()
end
- def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do
- was_superuser_before_update = User.superuser?(user)
-
+ def update_and_set_cache(changeset) do
with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do
set_cache(user)
end
- |> maybe_remove_report_notifications(was_superuser_before_update)
- end
-
- defp maybe_remove_report_notifications({:ok, %Pleroma.User{} = user} = result, true) do
- if not User.superuser?(user),
- do: user |> Notification.destroy_multiple_from_types(["pleroma:report"])
-
- result
- end
-
- defp maybe_remove_report_notifications(result, _) do
- result
end
def get_user_friends_ap_ids(user) do
@@ -2265,6 +2286,11 @@ def all_superusers do
|> Repo.all()
end
+ @spec all_users_with_privilege(atom()) :: [User.t()]
+ def all_users_with_privilege(privilege) do
+ User.Query.build(%{is_privileged: privilege}) |> Repo.all()
+ end
+
def muting_reblogs?(%User{} = user, %User{} = target) do
UserRelationship.reblog_mute_exists?(user, target)
end
diff --git a/lib/pleroma/user/query.ex b/lib/pleroma/user/query.ex
index 20bc1ea61..3e090cac0 100644
--- a/lib/pleroma/user/query.ex
+++ b/lib/pleroma/user/query.ex
@@ -29,6 +29,7 @@ defmodule Pleroma.User.Query do
import Ecto.Query
import Pleroma.Web.Utils.Guards, only: [not_empty_string: 1]
+ alias Pleroma.Config
alias Pleroma.FollowingRelationship
alias Pleroma.User
@@ -49,6 +50,7 @@ defmodule Pleroma.User.Query do
is_suggested: boolean(),
is_discoverable: boolean(),
super_users: boolean(),
+ is_privileged: atom(),
invisible: boolean(),
internal: boolean(),
followers: User.t(),
@@ -136,6 +138,43 @@ defp compose_query({:super_users, _}, query) do
)
end
+ defp compose_query({:is_privileged, privilege}, query) do
+ moderator_privileged = privilege in Config.get([:instance, :moderator_privileges])
+ admin_privileged = privilege in Config.get([:instance, :admin_privileges])
+
+ query = compose_query({:active, true}, query)
+ query = compose_query({:local, true}, query)
+
+ case {admin_privileged, moderator_privileged} do
+ {false, false} ->
+ where(
+ query,
+ false
+ )
+
+ {true, true} ->
+ where(
+ query,
+ [u],
+ u.is_admin or u.is_moderator
+ )
+
+ {true, false} ->
+ where(
+ query,
+ [u],
+ u.is_admin
+ )
+
+ {false, true} ->
+ where(
+ query,
+ [u],
+ u.is_moderator
+ )
+ end
+ end
+
defp compose_query({:local, _}, query), do: location_query(query, true)
defp compose_query({:external, _}, query), do: location_query(query, false)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index fa251394b..b9206b4da 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -401,11 +401,11 @@ defp do_flag(
_ <- notify_and_stream(activity),
:ok <-
maybe_federate(stripped_activity) do
- User.all_superusers()
+ User.all_users_with_privilege(:reports_manage_reports)
|> Enum.filter(fn user -> user.ap_id != actor end)
|> Enum.filter(fn user -> not is_nil(user.email) end)
- |> Enum.each(fn superuser ->
- superuser
+ |> Enum.each(fn privileged_user ->
+ privileged_user
|> Pleroma.Emails.AdminEmail.report(actor, account, statuses, content)
|> Pleroma.Emails.Mailer.deliver_async()
end)
diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
index 704b3abc9..1c5b1a059 100644
--- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex
@@ -136,11 +136,11 @@ def same_domain?(cng, fields \\ [:actor, :object]) do
# This figures out if a user is able to create, delete or modify something
# based on the domain and superuser status
- @spec validate_modification_rights(Ecto.Changeset.t()) :: Ecto.Changeset.t()
- def validate_modification_rights(cng) do
+ @spec validate_modification_rights(Ecto.Changeset.t(), atom()) :: Ecto.Changeset.t()
+ def validate_modification_rights(cng, privilege) do
actor = User.get_cached_by_ap_id(get_field(cng, :actor))
- if User.superuser?(actor) || same_domain?(cng) do
+ if User.privileged?(actor, privilege) || same_domain?(cng) do
cng
else
cng
diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
index 035fd5bc9..4d8502ada 100644
--- a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex
@@ -61,7 +61,7 @@ defp validate_data(cng) do
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|> validate_inclusion(:type, ["Delete"])
|> validate_delete_actor(:actor)
- |> validate_modification_rights()
+ |> validate_modification_rights(:messages_delete)
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|> add_deleted_activity_id()
end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
index 62ab6b69c..89cc0d6fe 100644
--- a/lib/pleroma/web/common_api.ex
+++ b/lib/pleroma/web/common_api.ex
@@ -145,10 +145,10 @@ def delete(activity_id, user) do
{:find_activity, Activity.get_by_id(activity_id)},
{_, %Object{} = object, _} <-
{:find_object, Object.normalize(activity, fetch: false), activity},
- true <- User.superuser?(user) || user.ap_id == object.data["actor"],
+ true <- User.privileged?(user, :messages_delete) || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
- if User.superuser?(user) and user.ap_id != object.data["actor"] do
+ if User.privileged?(user, :messages_delete) and user.ap_id != object.data["actor"] do
action =
if object.data["type"] == "ChatMessage" do
"chat_message_delete"
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index b4d092eed..467dc2fac 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -61,7 +61,20 @@ def get_friends(user, params \\ %{}) do
end
def get_notifications(user, params \\ %{}) do
- options = cast_params(params)
+ options =
+ cast_params(params) |> Map.update(:include_types, [], fn include_types -> include_types end)
+
+ options =
+ if ("pleroma:report" not in options.include_types and
+ User.privileged?(user, :reports_manage_reports)) or
+ User.privileged?(user, :reports_manage_reports) do
+ options
+ else
+ options
+ |> Map.update(:exclude_types, ["pleroma:report"], fn current_exclude_types ->
+ current_exclude_types ++ ["pleroma:report"]
+ end)
+ end
user
|> Notification.for_user_query(options)
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 2260bf5da..cc3e3582f 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -370,19 +370,22 @@ defp maybe_put_chat_token(data, %User{id: id}, %User{id: id}, %{
defp maybe_put_chat_token(data, _, _, _), do: data
defp maybe_put_role(data, %User{show_role: true} = user, _) do
- data
- |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
- |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
+ put_role(data, user)
end
defp maybe_put_role(data, %User{id: user_id} = user, %User{id: user_id}) do
- data
- |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
- |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
+ put_role(data, user)
end
defp maybe_put_role(data, _, _), do: data
+ defp put_role(data, user) do
+ data
+ |> Kernel.put_in([:pleroma, :is_admin], user.is_admin)
+ |> Kernel.put_in([:pleroma, :is_moderator], user.is_moderator)
+ |> Kernel.put_in([:pleroma, :privileges], User.privileges(user))
+ end
+
defp maybe_put_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
Kernel.put_in(
data,
@@ -399,12 +402,12 @@ defp maybe_put_allow_following_move(data, %User{id: user_id} = user, %User{id: u
defp maybe_put_allow_following_move(data, _, _), do: data
- defp maybe_put_activation_status(data, user, %User{is_admin: true}) do
- Kernel.put_in(data, [:pleroma, :deactivated], !user.is_active)
+ defp maybe_put_activation_status(data, user, user_for) do
+ if User.privileged?(user_for, :users_manage_activation_state),
+ do: Kernel.put_in(data, [:pleroma, :deactivated], !user.is_active),
+ else: data
end
- defp maybe_put_activation_status(data, _, _), do: data
-
defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
data
|> Kernel.put_in(
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index 1cc230316..917725839 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -48,7 +48,6 @@ def render("show.json", _) do
federation: federation(),
fields_limits: fields_limits(),
post_formats: Config.get([:instance, :allowed_post_formats]),
- privileged_staff: Config.get([:instance, :privileged_staff]),
birthday_required: Config.get([:instance, :birthday_required]),
birthday_min_age: Config.get([:instance, :birthday_min_age])
},
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex
index 62d445f34..9e27ac26c 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex
@@ -49,6 +49,10 @@ def get_nodeinfo("2.0") do
enabled: false
},
staffAccounts: staff_accounts,
+ roles: %{
+ admin: Config.get([:instance, :admin_privileges]),
+ moderator: Config.get([:instance, :moderator_privileges])
+ },
federation: federation,
pollLimits: Config.get([:instance, :poll_limits]),
postFormats: Config.get([:instance, :allowed_post_formats]),
@@ -69,8 +73,7 @@ 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),
- privilegedStaff: Config.get([:instance, :privileged_staff])
+ skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
}
}
end
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..f886c87ea
--- /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,
+ privilege
+ ) do
+ if (is_admin and privilege in Config.get([:instance, :admin_privileges])) or
+ (is_moderator and privilege 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/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex b/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex
deleted file mode 100644
index 3c2109496..000000000
--- a/lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex
+++ /dev/null
@@ -1,36 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2022 Pleroma Authors
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.Plugs.EnsureStaffPrivilegedPlug 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: true}}} = conn, _), do: conn
-
- def call(%{assigns: %{user: %User{is_moderator: true}}} = conn, _) do
- if Config.get!([:instance, :privileged_staff]) do
- conn
- else
- conn
- |> render_error(:forbidden, "User is not an admin.")
- |> halt()
- end
- end
-
- def call(conn, _) do
- conn
- |> render_error(:forbidden, "User is not a staff member.")
- |> halt()
- end
-end
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 2ea3ea7c1..ba1d64ab2 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -101,14 +101,80 @@ 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
+ pipeline :require_privileged_role_users_delete do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :users_delete)
+ end
+
+ pipeline :require_privileged_role_users_manage_credentials do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :users_manage_credentials)
+ end
+
+ pipeline :require_privileged_role_messages_read do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :messages_read)
+ end
+
+ pipeline :require_privileged_role_users_manage_tags do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :users_manage_tags)
+ end
+
+ pipeline :require_privileged_role_users_manage_activation_state do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :users_manage_activation_state)
+ end
+
+ pipeline :require_privileged_role_users_manage_invites do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :users_manage_invites)
+ end
+
+ pipeline :require_privileged_role_reports_manage_reports do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :reports_manage_reports)
+ end
+
+ pipeline :require_privileged_role_users_read do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :users_read)
+ end
+
+ pipeline :require_privileged_role_messages_delete do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :messages_delete)
+ end
+
+ pipeline :require_privileged_role_emoji_manage_emoji do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :emoji_manage_emoji)
+ end
+
+ pipeline :require_privileged_role_instances_delete do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :instances_delete)
+ end
+
+ pipeline :require_privileged_role_moderation_log_read do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :moderation_log_read)
+ end
+
+ pipeline :require_privileged_role_statistics_read do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :statistics_read)
+ end
+
+ pipeline :require_privileged_role_announcements_manage_announcements do
+ plug(:admin_api)
+ plug(Pleroma.Web.Plugs.EnsurePrivilegedPlug, :announcements_manage_announcements)
+ end
+
pipeline :pleroma_html do
plug(:browser)
plug(:authenticate)
@@ -167,8 +233,6 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
pipe_through([:admin_api, :require_admin])
- put("/users/disable_mfa", AdminAPIController, :disable_mfa)
-
get("/users/:nickname/permission_group", AdminAPIController, :right_get)
get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get)
@@ -199,17 +263,10 @@ defmodule Pleroma.Web.Router do
post("/relay", RelayController, :follow)
delete("/relay", RelayController, :unfollow)
- 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("/instance_document/:name", InstanceDocumentController, :show)
patch("/instance_document/:name", InstanceDocumentController, :update)
delete("/instance_document/:name", InstanceDocumentController, :delete)
- patch("/users/confirm_email", AdminAPIController, :confirm_email)
- patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
-
get("/config", ConfigController, :show)
post("/config", ConfigController, :update)
get("/config/descriptions", ConfigController, :descriptions)
@@ -229,6 +286,11 @@ defmodule Pleroma.Web.Router do
post("/frontends/install", FrontendController, :install)
post("/backups", AdminAPIController, :create_backup)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_announcements_manage_announcements)
get("/announcements", AnnouncementController, :index)
post("/announcements", AnnouncementController, :create)
@@ -237,14 +299,29 @@ defmodule Pleroma.Web.Router do
delete("/announcements/:id", AnnouncementController, :delete)
end
- # AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
- pipe_through([:admin_api, :require_privileged_staff])
+ pipe_through(:require_privileged_role_users_delete)
delete("/users", UserController, :delete)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_users_manage_credentials)
get("/users/:nickname/password_reset", AdminAPIController, :get_password_reset)
+ get("/users/:nickname/credentials", AdminAPIController, :show_user_credentials)
patch("/users/:nickname/credentials", AdminAPIController, :update_user_credentials)
+ put("/users/disable_mfa", AdminAPIController, :disable_mfa)
+ patch("/users/force_password_reset", AdminAPIController, :force_password_reset)
+ patch("/users/confirm_email", AdminAPIController, :confirm_email)
+ patch("/users/resend_confirmation_email", AdminAPIController, :resend_confirmation_email)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_messages_read)
get("/users/:nickname/statuses", AdminAPIController, :list_user_statuses)
get("/users/:nickname/chats", AdminAPIController, :list_user_chats)
@@ -253,52 +330,100 @@ defmodule Pleroma.Web.Router do
get("/chats/:id", ChatController, :show)
get("/chats/:id/messages", ChatController, :messages)
+
+ get("/instances/:instance/statuses", InstanceController, :list_statuses)
+
+ get("/statuses/:id", StatusController, :show)
end
- # AdminAPI: admins and mods (staff) can perform these actions
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
- pipe_through(:admin_api)
+ pipe_through(:require_privileged_role_users_manage_tags)
put("/users/tag", AdminAPIController, :tag_users)
delete("/users/tag", AdminAPIController, :untag_users)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_users_manage_activation_state)
patch("/users/:nickname/toggle_activation", UserController, :toggle_activation)
patch("/users/activate", UserController, :activate)
patch("/users/deactivate", UserController, :deactivate)
- patch("/users/approve", UserController, :approve)
+ end
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_users_manage_invites)
+
+ patch("/users/approve", UserController, :approve)
post("/users/invite_token", InviteController, :create)
get("/users/invites", InviteController, :index)
post("/users/revoke_invite", InviteController, :revoke)
post("/users/email_invite", InviteController, :email)
+ end
- get("/users", UserController, :index)
- get("/users/:nickname", UserController, :show)
-
- get("/instances/:instance/statuses", InstanceController, :list_statuses)
- delete("/instances/:instance", InstanceController, :delete)
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_reports_manage_reports)
get("/reports", ReportController, :index)
get("/reports/:id", ReportController, :show)
patch("/reports", ReportController, :update)
post("/reports/:id/notes", ReportController, :notes_create)
delete("/reports/:report_id/notes/:id", ReportController, :notes_delete)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_users_read)
+
+ get("/users", UserController, :index)
+ get("/users/:nickname", UserController, :show)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_messages_delete)
- get("/statuses/:id", StatusController, :show)
put("/statuses/:id", StatusController, :update)
delete("/statuses/:id", StatusController, :delete)
- get("/moderation_log", AdminAPIController, :list_log)
-
- post("/reload_emoji", AdminAPIController, :reload_emoji)
- get("/stats", AdminAPIController, :stats)
-
delete("/chats/:id/messages/:message_id", ChatController, :delete_message)
end
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_emoji_manage_emoji)
+
+ post("/reload_emoji", AdminAPIController, :reload_emoji)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_instances_delete)
+
+ delete("/instances/:instance", InstanceController, :delete)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_moderation_log_read)
+
+ get("/moderation_log", AdminAPIController, :list_log)
+ end
+
+ # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role)
+ scope "/api/v1/pleroma/admin", Pleroma.Web.AdminAPI do
+ pipe_through(:require_privileged_role_statistics_read)
+
+ get("/stats", AdminAPIController, :stats)
+ end
+
scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/pack" do
- pipe_through(:admin_api)
+ pipe_through(:require_privileged_role_emoji_manage_emoji)
post("/", EmojiPackController, :create)
patch("/", EmojiPackController, :update)
@@ -313,7 +438,7 @@ defmodule Pleroma.Web.Router do
# Modifying packs
scope "/packs" do
- pipe_through(:admin_api)
+ pipe_through(:require_privileged_role_emoji_manage_emoji)
get("/import", EmojiPackController, :import_from_filesystem)
get("/remote", EmojiPackController, :remote)
diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs
index a000c0efd..255097ed0 100644
--- a/test/pleroma/notification_test.exs
+++ b/test/pleroma/notification_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.NotificationTest do
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
import Pleroma.Factory
import Mock
@@ -32,20 +32,26 @@ test "never returns nil" do
refute {:ok, [nil]} == Notification.create_notifications(activity)
end
- test "creates a notification for a report" do
+ test "creates a report notification only for privileged users" do
reporting_user = insert(:user)
reported_user = insert(:user)
- {:ok, moderator_user} = insert(:user) |> User.admin_api_update(%{is_moderator: true})
+ moderator_user = insert(:user, is_moderator: true)
- {:ok, activity} = CommonAPI.report(reporting_user, %{account_id: reported_user.id})
+ clear_config([:instance, :moderator_privileges], [])
+ {:ok, activity1} = CommonAPI.report(reporting_user, %{account_id: reported_user.id})
+ {:ok, []} = Notification.create_notifications(activity1)
- {:ok, [notification]} = Notification.create_notifications(activity)
+ clear_config([:instance, :moderator_privileges], [:reports_manage_reports])
+ {:ok, activity2} = CommonAPI.report(reporting_user, %{account_id: reported_user.id})
+ {:ok, [notification]} = Notification.create_notifications(activity2)
assert notification.user_id == moderator_user.id
assert notification.type == "pleroma:report"
end
- test "suppresses notification to reporter if reporter is an admin" do
+ test "suppresses notifications for own reports" do
+ clear_config([:instance, :admin_privileges], [:reports_manage_reports])
+
reporting_admin = insert(:user, is_admin: true)
reported_user = insert(:user)
other_admin = insert(:user, is_admin: true)
@@ -542,25 +548,6 @@ test "it clears all notifications belonging to the user" do
end
end
- describe "destroy_multiple_from_types/2" do
- test "clears all notifications of a certain type for a given user" do
- report_activity = insert(:report_activity)
- user1 = insert(:user, is_moderator: true, is_admin: true)
- user2 = insert(:user, is_moderator: true, is_admin: true)
- {:ok, _} = Notification.create_notifications(report_activity)
-
- {:ok, _} =
- CommonAPI.post(user2, %{
- status: "hey @#{user1.nickname} !"
- })
-
- Notification.destroy_multiple_from_types(user1, ["pleroma:report"])
-
- assert [%Pleroma.Notification{type: "mention"}] = Notification.for_user(user1)
- assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user2)
- end
- end
-
describe "set_read_up_to()" do
test "it sets all notifications as read up to a specified notification ID" do
user = insert(:user)
diff --git a/test/pleroma/user/query_test.exs b/test/pleroma/user/query_test.exs
index bd45d1bca..30a4637f2 100644
--- a/test/pleroma/user/query_test.exs
+++ b/test/pleroma/user/query_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.User.QueryTest do
- use Pleroma.DataCase, async: true
+ use Pleroma.DataCase, async: false
alias Pleroma.Repo
alias Pleroma.User
@@ -44,4 +44,63 @@ test "is_suggested param" do
|> User.Query.build()
|> Repo.all()
end
+
+ describe "is_privileged param" do
+ setup do
+ %{
+ user: insert(:user, local: true, is_admin: false, is_moderator: false),
+ moderator_user: insert(:user, local: true, is_admin: false, is_moderator: true),
+ admin_user: insert(:user, local: true, is_admin: true, is_moderator: false),
+ admin_moderator_user: insert(:user, local: true, is_admin: true, is_moderator: true),
+ remote_user: insert(:user, local: false, is_admin: true, is_moderator: true),
+ non_active_user:
+ insert(:user, local: true, is_admin: true, is_moderator: true, is_active: false)
+ }
+ end
+
+ test "doesn't return any users when there are no privileged roles" do
+ clear_config([:instance, :admin_privileges], [])
+ clear_config([:instance, :moderator_privileges], [])
+
+ assert [] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ end
+
+ test "returns moderator users if they are privileged", %{
+ moderator_user: moderator_user,
+ admin_moderator_user: admin_moderator_user
+ } do
+ clear_config([:instance, :admin_privileges], [])
+ clear_config([:instance, :moderator_privileges], [:cofe])
+
+ assert [_, _] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ assert moderator_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ assert admin_moderator_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ end
+
+ test "returns admin users if they are privileged", %{
+ admin_user: admin_user,
+ admin_moderator_user: admin_moderator_user
+ } do
+ clear_config([:instance, :admin_privileges], [:cofe])
+ clear_config([:instance, :moderator_privileges], [])
+
+ assert [_, _] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ assert admin_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ assert admin_moderator_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ end
+
+ test "returns admin and moderator users if they are both privileged", %{
+ moderator_user: moderator_user,
+ admin_user: admin_user,
+ admin_moderator_user: admin_moderator_user
+ } do
+ clear_config([:instance, :admin_privileges], [:cofe])
+ clear_config([:instance, :moderator_privileges], [:cofe])
+
+ assert [_, _, _] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ assert admin_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ assert moderator_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ assert admin_moderator_user in (User.Query.build(%{is_privileged: :cofe}) |> Repo.all())
+ end
+ end
end
diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs
index a58c8f68b..c16312a65 100644
--- a/test/pleroma/user_test.exs
+++ b/test/pleroma/user_test.exs
@@ -5,7 +5,6 @@
defmodule Pleroma.UserTest do
alias Pleroma.Activity
alias Pleroma.Builders.UserBuilder
- alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
@@ -13,7 +12,7 @@ defmodule Pleroma.UserTest do
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
@@ -473,12 +472,7 @@ test "it sends a welcome chat message if it is set" do
reject_deletes: []
)
- setup do:
- clear_config(:mrf,
- policies: [
- Pleroma.Web.ActivityPub.MRF.SimplePolicy
- ]
- )
+ setup do: clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
test "it sends a welcome chat message when Simple policy applied to local instance" do
clear_config([:mrf_simple, :media_nsfw], [{"localhost", ""}])
@@ -2038,31 +2032,82 @@ test "returns :approval_pending for unapproved user" do
end
end
- describe "superuser?/1" do
+ describe "privileged?/1" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:cofe, :suya])
+ clear_config([:instance, :moderator_privileges], [:cofe, :suya])
+ end
+
test "returns false for unprivileged users" do
user = insert(:user, local: true)
- refute User.superuser?(user)
+ refute User.privileged?(user, :cofe)
end
test "returns false for remote users" do
user = insert(:user, local: false)
remote_admin_user = insert(:user, local: false, is_admin: true)
- refute User.superuser?(user)
- refute User.superuser?(remote_admin_user)
+ refute User.privileged?(user, :cofe)
+ refute User.privileged?(remote_admin_user, :cofe)
end
- test "returns true for local moderators" do
+ test "returns true for local moderators if, and only if, they are privileged" do
user = insert(:user, local: true, is_moderator: true)
- assert User.superuser?(user)
+ assert User.privileged?(user, :cofe)
+
+ clear_config([:instance, :moderator_privileges], [])
+
+ refute User.privileged?(user, :cofe)
end
- test "returns true for local admins" do
+ test "returns true for local admins if, and only if, they are privileged" do
user = insert(:user, local: true, is_admin: true)
- assert User.superuser?(user)
+ assert User.privileged?(user, :cofe)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ refute User.privileged?(user, :cofe)
+ end
+ end
+
+ describe "privileges/1" do
+ setup do
+ clear_config([:instance, :moderator_privileges], [:cofe, :only_moderator])
+ clear_config([:instance, :admin_privileges], [:cofe, :only_admin])
+ end
+
+ test "returns empty list for users without roles" do
+ user = insert(:user, local: true)
+
+ assert [] == User.privileges(user)
+ end
+
+ test "returns list of privileges for moderators" do
+ moderator = insert(:user, is_moderator: true, local: true)
+
+ assert [:cofe, :only_moderator] == User.privileges(moderator) |> Enum.sort()
+ end
+
+ test "returns list of privileges for admins" do
+ admin = insert(:user, is_admin: true, local: true)
+
+ assert [:cofe, :only_admin] == User.privileges(admin) |> Enum.sort()
+ end
+
+ test "returns list of unique privileges for users who are both moderator and admin" do
+ moderator_admin = insert(:user, is_moderator: true, is_admin: true, local: true)
+
+ assert [:cofe, :only_admin, :only_moderator] ==
+ User.privileges(moderator_admin) |> Enum.sort()
+ end
+
+ test "returns empty list for remote users" do
+ remote_moderator_admin = insert(:user, is_moderator: true, is_admin: true, local: false)
+
+ assert [] == User.privileges(remote_moderator_admin)
end
end
@@ -2105,13 +2150,77 @@ test "returns true when the account is unconfirmed and confirmation is required
assert User.visible_for(user, other_user) == :visible
end
- test "returns true when the account is unconfirmed and being viewed by a privileged account (confirmation required)" do
+ test "returns true when the account is unconfirmed and being viewed by a privileged account (privilege :users_manage_activation_state, confirmation required)" do
clear_config([:instance, :account_activation_required], true)
+ clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
user = insert(:user, local: true, is_confirmed: false)
other_user = insert(:user, local: true, is_admin: true)
assert User.visible_for(user, other_user) == :visible
+
+ clear_config([:instance, :admin_privileges], [])
+
+ refute User.visible_for(user, other_user) == :visible
+ end
+ end
+
+ describe "all_users_with_privilege/1" do
+ setup do
+ %{
+ user: insert(:user, local: true, is_admin: false, is_moderator: false),
+ moderator_user: insert(:user, local: true, is_admin: false, is_moderator: true),
+ admin_user: insert(:user, local: true, is_admin: true, is_moderator: false),
+ admin_moderator_user: insert(:user, local: true, is_admin: true, is_moderator: true),
+ remote_user: insert(:user, local: false, is_admin: true, is_moderator: true),
+ non_active_user:
+ insert(:user, local: true, is_admin: true, is_moderator: true, is_active: false)
+ }
+ end
+
+ test "doesn't return any users when there are no privileged roles" do
+ clear_config([:instance, :admin_privileges], [])
+ clear_config([:instance, :moderator_privileges], [])
+
+ assert [] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ end
+
+ test "returns moderator users if they are privileged", %{
+ moderator_user: moderator_user,
+ admin_moderator_user: admin_moderator_user
+ } do
+ clear_config([:instance, :admin_privileges], [])
+ clear_config([:instance, :moderator_privileges], [:cofe])
+
+ assert [_, _] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ assert moderator_user in User.all_users_with_privilege(:cofe)
+ assert admin_moderator_user in User.all_users_with_privilege(:cofe)
+ end
+
+ test "returns admin users if they are privileged", %{
+ admin_user: admin_user,
+ admin_moderator_user: admin_moderator_user
+ } do
+ clear_config([:instance, :admin_privileges], [:cofe])
+ clear_config([:instance, :moderator_privileges], [])
+
+ assert [_, _] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ assert admin_user in User.all_users_with_privilege(:cofe)
+ assert admin_moderator_user in User.all_users_with_privilege(:cofe)
+ end
+
+ test "returns admin and moderator users if they are both privileged", %{
+ moderator_user: moderator_user,
+ admin_user: admin_user,
+ admin_moderator_user: admin_moderator_user
+ } do
+ clear_config([:instance, :admin_privileges], [:cofe])
+ clear_config([:instance, :moderator_privileges], [:cofe])
+
+ assert [_, _, _] = User.Query.build(%{is_privileged: :cofe}) |> Repo.all()
+ assert admin_user in User.all_users_with_privilege(:cofe)
+ assert moderator_user in User.all_users_with_privilege(:cofe)
+ assert admin_moderator_user in User.all_users_with_privilege(:cofe)
end
end
@@ -2351,26 +2460,6 @@ test "performs update cache if user updated" do
assert {:ok, user} = Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
assert %User{bio: "test-bio"} = User.get_cached_by_ap_id(user.ap_id)
end
-
- test "removes report notifs when user isn't superuser any more" do
- report_activity = insert(:report_activity)
- user = insert(:user, is_moderator: true, is_admin: true)
- {:ok, _} = Notification.create_notifications(report_activity)
-
- assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user)
-
- {:ok, user} = user |> User.admin_api_update(%{is_moderator: false})
- # is still superuser because still admin
- assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user)
-
- {:ok, user} = user |> User.admin_api_update(%{is_moderator: true, is_admin: false})
- # is still superuser because still moderator
- assert [%Pleroma.Notification{type: "pleroma:report"}] = Notification.for_user(user)
-
- {:ok, user} = user |> User.admin_api_update(%{is_moderator: false})
- # is not a superuser any more
- assert [] = Notification.for_user(user)
- end
end
describe "following/followers synchronization" do
diff --git a/test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs
index ea4664859..bbb31516c 100644
--- a/test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs
+++ b/test/pleroma/web/activity_pub/object_validators/delete_validation_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidationTest do
- use Pleroma.DataCase, async: true
+ use Pleroma.DataCase, async: false
alias Pleroma.Object
alias Pleroma.Web.ActivityPub.Builder
@@ -90,17 +90,26 @@ test "it's invalid if the actor of the object and the actor of delete are from d
assert {:actor, {"is not allowed to modify object", []}} in cng.errors
end
- test "it's valid if the actor of the object is a local superuser",
+ test "it's only valid if the actor of the object is a privileged local user",
%{valid_post_delete: valid_post_delete} do
+ clear_config([:instance, :moderator_privileges], [:messages_delete])
+
user =
insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
- valid_other_actor =
+ post_delete_with_moderator_actor =
valid_post_delete
|> Map.put("actor", user.ap_id)
- {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
+ {:ok, _, meta} = ObjectValidator.validate(post_delete_with_moderator_actor, [])
+
assert meta[:do_not_federate]
+
+ clear_config([:instance, :moderator_privileges], [])
+
+ {:error, cng} = ObjectValidator.validate(post_delete_with_moderator_actor, [])
+
+ assert {:actor, {"is not allowed to modify object", []}} in cng.errors
end
end
end
diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
index d83f7f011..e1ab50542 100644
--- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import ExUnit.CaptureLog
@@ -92,18 +92,12 @@ test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or bro
describe "PUT /api/pleroma/admin/users/tag" do
setup %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_tags])
+
user1 = insert(:user, %{tags: ["x"]})
user2 = insert(:user, %{tags: ["y"]})
user3 = insert(:user, %{tags: ["unchanged"]})
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> put(
- "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
- "#{user2.nickname}&tags[]=foo&tags[]=bar"
- )
-
%{conn: conn, user1: user1, user2: user2, user3: user3}
end
@@ -113,6 +107,14 @@ test "it appends specified tags to users with specified nicknames", %{
user1: user1,
user2: user2
} do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> put(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
+ "#{user2.nickname}&tags[]=foo&tags[]=bar"
+ )
+
assert empty_json_response(conn)
assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
@@ -130,26 +132,43 @@ test "it appends specified tags to users with specified nicknames", %{
"@#{admin.nickname} added tags: #{tags} to users: #{users}"
end
- test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+ test "it does not modify tags of not specified users", %{
+ conn: conn,
+ user1: user1,
+ user2: user2,
+ user3: user3
+ } do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> put(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
+ "#{user2.nickname}&tags[]=foo&tags[]=bar"
+ )
+
assert empty_json_response(conn)
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
end
+
+ test "it requires privileged role :users_manage_tags", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> put("/api/pleroma/admin/users/tag?nicknames[]=nickname&tags[]=foo&tags[]=bar")
+
+ assert json_response(response, :forbidden)
+ end
end
describe "DELETE /api/pleroma/admin/users/tag" do
setup %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_tags])
user1 = insert(:user, %{tags: ["x"]})
user2 = insert(:user, %{tags: ["y", "z"]})
user3 = insert(:user, %{tags: ["unchanged"]})
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete(
- "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
- "#{user2.nickname}&tags[]=x&tags[]=z"
- )
-
%{conn: conn, user1: user1, user2: user2, user3: user3}
end
@@ -159,6 +178,14 @@ test "it removes specified tags from users with specified nicknames", %{
user1: user1,
user2: user2
} do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
+ "#{user2.nickname}&tags[]=x&tags[]=z"
+ )
+
assert empty_json_response(conn)
assert User.get_cached_by_id(user1.id).tags == []
assert User.get_cached_by_id(user2.id).tags == ["y"]
@@ -176,10 +203,34 @@ test "it removes specified tags from users with specified nicknames", %{
"@#{admin.nickname} removed tags: #{tags} from users: #{users}"
end
- test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+ test "it does not modify tags of not specified users", %{
+ conn: conn,
+ user1: user1,
+ user2: user2,
+ user3: user3
+ } do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
+ "#{user2.nickname}&tags[]=x&tags[]=z"
+ )
+
assert empty_json_response(conn)
assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
end
+
+ test "it requires privileged role :users_manage_tags", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/tag?nicknames[]=nickname&tags[]=foo&tags[]=bar")
+
+ assert json_response(response, :forbidden)
+ end
end
describe "/api/pleroma/admin/users/:nickname/permission_group" do
@@ -271,21 +322,38 @@ test "/:right DELETE, can remove from a permission group (multiple)", %{
end
end
- test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
- user = insert(:user)
+ describe "/api/pleroma/admin/users/:nickname/password_reset" do
+ test "it returns a password reset link", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
+ user = insert(:user)
- resp = json_response(conn, 200)
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
- assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
+ resp = json_response(conn, 200)
+
+ assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
+ end
+
+ test "it requires privileged role :users_manage_credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/api/pleroma/admin/users/nickname/password_reset")
+
+ assert json_response(response, :forbidden)
+ end
end
describe "PUT disable_mfa" do
test "returns 200 and disable 2fa", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
+
user =
insert(:user,
multi_factor_authentication_settings: %MFA.Settings{
@@ -307,6 +375,8 @@ test "returns 200 and disable 2fa", %{conn: conn} do
end
test "returns 404 if user not found", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
+
response =
conn
|> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
@@ -314,6 +384,16 @@ test "returns 404 if user not found", %{conn: conn} do
assert response == %{"error" => "Not found"}
end
+
+ test "it requires privileged role :users_manage_credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
+
+ assert json_response(response, :forbidden)
+ end
end
describe "GET /api/pleroma/admin/restart" do
@@ -344,6 +424,8 @@ test "need_reboot flag", %{conn: conn} do
describe "GET /api/pleroma/admin/users/:nickname/statuses" do
setup do
+ clear_config([:instance, :admin_privileges], [:messages_read])
+
user = insert(:user)
insert(:note_activity, user: user)
@@ -360,6 +442,14 @@ test "renders user's statuses", %{conn: conn, user: user} do
assert length(activities) == 3
end
+ test "it requires privileged role :messages_read", %{conn: conn, user: user} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
+
+ assert json_response(conn, :forbidden)
+ end
+
test "renders user's statuses with pagination", %{conn: conn, user: user} do
%{"total" => 3, "activities" => [activity1]} =
conn
@@ -421,21 +511,32 @@ test "excludes reblogs by default", %{conn: conn, user: user} do
describe "GET /api/pleroma/admin/users/:nickname/chats" do
setup do
+ clear_config([:instance, :admin_privileges], [:messages_read])
+
user = insert(:user)
+
+ %{user: user}
+ end
+
+ test "renders user's chats", %{conn: conn, user: user} do
recipients = insert_list(3, :user)
Enum.each(recipients, fn recipient ->
CommonAPI.post_chat_message(user, recipient, "yo")
end)
- %{user: user}
- end
-
- test "renders user's chats", %{conn: conn, user: user} do
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
assert json_response(conn, 200) |> length() == 3
end
+
+ test "it requires privileged role :messages_read", %{conn: conn, user: user} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
+
+ assert json_response(conn, :forbidden)
+ end
end
describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do
@@ -471,6 +572,7 @@ test "returns 403", %{conn: conn, user: user} do
describe "GET /api/pleroma/admin/moderation_log" do
setup do
+ clear_config([:instance, :admin_privileges], [:moderation_log_read])
moderator = insert(:user, is_moderator: true)
%{moderator: moderator}
@@ -675,6 +777,15 @@ test "returns log filtered by search", %{conn: conn, moderator: moderator} do
assert get_in(first_entry, ["data", "message"]) ==
"@#{moderator.nickname} unfollowed relay: https://example.org/relay"
end
+
+ test "it requires privileged role :moderation_log_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> get("/api/pleroma/admin/moderation_log")
+ |> json_response(:forbidden)
+ end
end
test "gets a remote users when [:instance, :limit_to_local_content] is set to :unauthenticated",
@@ -688,6 +799,7 @@ test "gets a remote users when [:instance, :limit_to_local_content] is set to :u
describe "GET /users/:nickname/credentials" do
test "gets the user credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
user = insert(:user)
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
@@ -696,6 +808,7 @@ test "gets the user credentials", %{conn: conn} do
end
test "returns 403 if requested by a non-admin" do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
user = insert(:user)
conn =
@@ -705,6 +818,16 @@ test "returns 403 if requested by a non-admin" do
assert json_response(conn, :forbidden)
end
+
+ test "it requires privileged role :users_manage_credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/users/nickname/credentials")
+
+ assert json_response(response, :forbidden)
+ end
end
describe "PATCH /users/:nickname/credentials" do
@@ -714,6 +837,8 @@ test "returns 403 if requested by a non-admin" do
end
test "changes password and email", %{conn: conn, admin: admin, user: user} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
+
assert user.password_reset_pending == false
conn =
@@ -756,6 +881,19 @@ test "returns 403 if requested by a non-admin", %{user: user} do
assert json_response(conn, :forbidden)
end
+ test "returns 403 if not privileged with :users_manage_credentials", %{conn: conn, user: user} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
+ "password" => "new_password",
+ "email" => "new_email@example.com",
+ "name" => "new_name"
+ })
+
+ assert json_response(conn, :forbidden)
+ end
+
test "changes actor type from permitted list", %{conn: conn, user: user} do
assert user.actor_type == "Person"
@@ -784,6 +922,7 @@ test "update non existing user", %{conn: conn} do
describe "PATCH /users/:nickname/force_password_reset" do
test "sets password_reset_pending to true", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
user = insert(:user)
assert user.password_reset_pending == false
@@ -796,10 +935,21 @@ test "sets password_reset_pending to true", %{conn: conn} do
assert User.get_by_id(user.id).password_reset_pending == true
end
+
+ test "it requires privileged role :users_manage_credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> patch("/api/pleroma/admin/users/force_password_reset", %{nickname: "nickname"})
+
+ assert json_response(response, :forbidden)
+ end
end
describe "PATCH /confirm_email" do
test "it confirms emails of two users", %{conn: conn, admin: admin} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
[first_user, second_user] = insert_pair(:user, is_confirmed: false)
refute first_user.is_confirmed
@@ -826,10 +976,21 @@ test "it confirms emails of two users", %{conn: conn, admin: admin} do
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{second_user.nickname}"
end
+
+ test "it requires privileged role :users_manage_credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> patch("/api/pleroma/admin/users/confirm_email", %{nicknames: ["nickname"]})
+
+ assert json_response(response, :forbidden)
+ end
end
describe "PATCH /resend_confirmation_email" do
test "it resend emails for two users", %{conn: conn, admin: admin} do
+ clear_config([:instance, :admin_privileges], [:users_manage_credentials])
[first_user, second_user] = insert_pair(:user, is_confirmed: false)
ret_conn =
@@ -855,9 +1016,23 @@ test "it resend emails for two users", %{conn: conn, admin: admin} do
|> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
|> assert_email_sent()
end
+
+ test "it requires privileged role :users_manage_credentials", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> patch("/api/pleroma/admin/users/resend_confirmation_email", %{nicknames: ["nickname"]})
+
+ assert json_response(response, :forbidden)
+ end
end
describe "/api/pleroma/admin/stats" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:statistics_read])
+ end
+
test "status visibility count", %{conn: conn} do
user = insert(:user)
CommonAPI.post(user, %{visibility: "public", status: "hey"})
@@ -890,6 +1065,14 @@ test "by instance", %{conn: conn} do
assert %{"direct" => 0, "private" => 1, "public" => 0, "unlisted" => 1} =
response["status_visibility"]
end
+
+ test "it requires privileged role :statistics_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> get("/api/pleroma/admin/stats", instance: "lain.wired")
+ |> json_response(:forbidden)
+ end
end
describe "/api/pleroma/backups" do
@@ -958,6 +1141,34 @@ test "it doesn't limit admins", %{conn: conn} do
assert Repo.aggregate(Pleroma.User.Backup, :count) == 2
end
end
+
+ describe "POST /api/v1/pleroma/admin/reload_emoji" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
+
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ {:ok, %{conn: conn, admin: admin}}
+ end
+
+ test "it requires privileged role :emoji_manage_emoji", %{conn: conn} do
+ assert conn
+ |> post("/api/v1/pleroma/admin/reload_emoji")
+ |> json_response(200)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> post("/api/v1/pleroma/admin/reload_emoji")
+ |> json_response(:forbidden)
+ end
+ end
end
# Needed for testing
diff --git a/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs b/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
index 5b8148c05..cf60bcad5 100644
--- a/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/announcement_controller_test.exs
@@ -3,11 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.AnnouncementControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
setup do
+ clear_config([:instance, :admin_privileges], [:announcements_manage_announcements])
admin = insert(:user, is_admin: true)
token = insert(:oauth_admin_token, user: admin)
@@ -31,6 +32,18 @@ test "it lists all announcements", %{conn: conn} do
assert [%{"id" => ^id}] = response
end
+ test "it requires privileged role :announcements_manage_announcements", %{conn: conn} do
+ conn
+ |> get("/api/v1/pleroma/admin/announcements")
+ |> json_response_and_validate_schema(:ok)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements")
+ |> json_response(:forbidden)
+ end
+
test "it paginates announcements", %{conn: conn} do
_announcements = Enum.map(0..20, fn _ -> insert(:announcement) end)
@@ -92,6 +105,20 @@ test "it displays one announcement", %{conn: conn} do
assert %{"id" => ^id} = response
end
+ test "it requires privileged role :announcements_manage_announcements", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements/#{id}")
+ |> json_response_and_validate_schema(:ok)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn
+ |> get("/api/v1/pleroma/admin/announcements/#{id}")
+ |> json_response(:forbidden)
+ end
+
test "it returns not found for non-existent id", %{conn: conn} do
%{id: id} = insert(:announcement)
@@ -112,6 +139,20 @@ test "it deletes specified announcement", %{conn: conn} do
|> json_response_and_validate_schema(:ok)
end
+ test "it requires privileged role :announcements_manage_announcements", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ conn
+ |> delete("/api/v1/pleroma/admin/announcements/#{id}")
+ |> json_response_and_validate_schema(:ok)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn
+ |> delete("/api/v1/pleroma/admin/announcements/#{id}")
+ |> json_response(:forbidden)
+ end
+
test "it returns not found for non-existent id", %{conn: conn} do
%{id: id} = insert(:announcement)
@@ -156,6 +197,29 @@ test "it updates a field", %{conn: conn} do
assert NaiveDateTime.compare(new.starts_at, starts_at) == :eq
end
+ test "it requires privileged role :announcements_manage_announcements", %{conn: conn} do
+ %{id: id} = insert(:announcement)
+
+ now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+ starts_at = NaiveDateTime.add(now, -10, :second)
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
+ starts_at: NaiveDateTime.to_iso8601(starts_at)
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/v1/pleroma/admin/announcements/#{id}", %{
+ starts_at: NaiveDateTime.to_iso8601(starts_at)
+ })
+ |> json_response(:forbidden)
+ end
+
test "it updates with time with utc timezone", %{conn: conn} do
%{id: id} = insert(:announcement)
@@ -250,6 +314,36 @@ test "it creates an announcement", %{conn: conn} do
assert NaiveDateTime.compare(announcement.ends_at, ends_at) == :eq
end
+ test "it requires privileged role :announcements_manage_announcements", %{conn: conn} do
+ content = "test post announcement api"
+
+ now = NaiveDateTime.utc_now() |> NaiveDateTime.truncate(:second)
+ starts_at = NaiveDateTime.add(now, -10, :second)
+ ends_at = NaiveDateTime.add(now, 10, :second)
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/admin/announcements", %{
+ "content" => content,
+ "starts_at" => NaiveDateTime.to_iso8601(starts_at),
+ "ends_at" => NaiveDateTime.to_iso8601(ends_at),
+ "all_day" => true
+ })
+ |> json_response_and_validate_schema(:ok)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/v1/pleroma/admin/announcements", %{
+ "content" => content,
+ "starts_at" => NaiveDateTime.to_iso8601(starts_at),
+ "ends_at" => NaiveDateTime.to_iso8601(ends_at),
+ "all_day" => true
+ })
+ |> json_response(:forbidden)
+ end
+
test "creating with time with utc timezones", %{conn: conn} do
content = "test post announcement api"
diff --git a/test/pleroma/web/admin_api/controllers/chat_controller_test.exs b/test/pleroma/web/admin_api/controllers/chat_controller_test.exs
index 0ef7c367b..1b5c31b7d 100644
--- a/test/pleroma/web/admin_api/controllers/chat_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/chat_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ChatControllerTest do
- use Pleroma.Web.ConnCase, async: true
+ use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
@@ -27,7 +27,10 @@ defp admin_setup do
end
describe "DELETE /api/pleroma/admin/chats/:id/messages/:message_id" do
- setup do: admin_setup()
+ setup do
+ clear_config([:instance, :admin_privileges], [:messages_delete])
+ admin_setup()
+ end
test "it deletes a message from the chat", %{conn: conn, admin: admin} do
user = insert(:user)
@@ -60,10 +63,22 @@ test "it deletes a message from the chat", %{conn: conn, admin: admin} do
refute MessageReference.get_by_id(recipient_cm_ref.id)
assert %{data: %{"type" => "Tombstone"}} = Object.get_by_id(object.id)
end
+
+ test "it requires privileged role :messages_delete", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> delete("/api/pleroma/admin/chats/some_id/messages/some_ref_id")
+ |> json_response(:forbidden)
+ end
end
describe "GET /api/pleroma/admin/chats/:id/messages" do
- setup do: admin_setup()
+ setup do
+ clear_config([:instance, :admin_privileges], [:messages_read])
+ admin_setup()
+ end
test "it paginates", %{conn: conn} do
user = insert(:user)
@@ -114,10 +129,21 @@ test "it returns the messages for a given chat", %{conn: conn} do
assert length(result) == 3
end
+
+ test "it requires privileged role :messages_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/chats/some_id/messages")
+
+ assert json_response(conn, :forbidden)
+ end
end
describe "GET /api/pleroma/admin/chats/:id" do
- setup do: admin_setup()
+ setup do
+ clear_config([:instance, :admin_privileges], [:messages_read])
+ admin_setup()
+ end
test "it returns a chat", %{conn: conn} do
user = insert(:user)
@@ -135,6 +161,14 @@ test "it returns a chat", %{conn: conn} do
assert %{} = result["receiver"]
refute result["account"]
end
+
+ test "it requires privileged role :messages_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/chats/some_id")
+
+ assert json_response(conn, :forbidden)
+ end
end
describe "unauthorized chat moderation" do
diff --git a/test/pleroma/web/admin_api/controllers/instance_controller_test.exs b/test/pleroma/web/admin_api/controllers/instance_controller_test.exs
index 72436cd83..6cca623f3 100644
--- a/test/pleroma/web/admin_api/controllers/instance_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/instance_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import Pleroma.Factory
@@ -31,6 +31,7 @@ defmodule Pleroma.Web.AdminAPI.InstanceControllerTest do
end
test "GET /instances/:instance/statuses", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:messages_read])
user = insert(:user, local: false, ap_id: "https://archae.me/users/archaeme")
user2 = insert(:user, local: false, ap_id: "https://test.com/users/test")
insert_pair(:note_activity, user: user)
@@ -60,9 +61,14 @@ test "GET /instances/:instance/statuses", %{conn: conn} do
|> json_response(200)
assert length(activities) == 3
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn |> get("/api/pleroma/admin/instances/archae.me/statuses") |> json_response(:forbidden)
end
test "DELETE /instances/:instance", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:instances_delete])
user = insert(:user, nickname: "lain@lain.com")
post = insert(:note_activity, user: user)
@@ -76,5 +82,11 @@ test "DELETE /instances/:instance", %{conn: conn} do
assert response == "lain.com"
refute Repo.reload(user).is_active
refute Repo.reload(post)
+
+ clear_config([:instance, :admin_privileges], [])
+
+ conn
+ |> delete("/api/pleroma/admin/instances/lain.com")
+ |> json_response(:forbidden)
end
end
diff --git a/test/pleroma/web/admin_api/controllers/invite_controller_test.exs b/test/pleroma/web/admin_api/controllers/invite_controller_test.exs
index b9d48a4b6..8051cb2e9 100644
--- a/test/pleroma/web/admin_api/controllers/invite_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/invite_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.InviteControllerTest do
- use Pleroma.Web.ConnCase, async: true
+ use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
@@ -23,8 +23,25 @@ defmodule Pleroma.Web.AdminAPI.InviteControllerTest do
end
describe "POST /api/pleroma/admin/users/email_invite, with valid config" do
- setup do: clear_config([:instance, :registrations_open], false)
- setup do: clear_config([:instance, :invites_enabled], true)
+ setup do
+ clear_config([:instance, :registrations_open], false)
+ clear_config([:instance, :invites_enabled], true)
+ clear_config([:instance, :admin_privileges], [:users_manage_invites])
+ end
+
+ test "returns 403 if not privileged with :users_manage_invites", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json;charset=utf-8")
+ |> post("/api/pleroma/admin/users/email_invite", %{
+ email: "foo@bar.com",
+ name: "J. D."
+ })
+
+ assert json_response(conn, :forbidden)
+ end
test "sends invitation and returns 204", %{admin: admin, conn: conn} do
recipient_email = "foo@bar.com"
@@ -114,8 +131,11 @@ test "email with +", %{conn: conn, admin: admin} do
end
describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
- setup do: clear_config([:instance, :registrations_open])
- setup do: clear_config([:instance, :invites_enabled])
+ setup do
+ clear_config([:instance, :registrations_open])
+ clear_config([:instance, :invites_enabled])
+ clear_config([:instance, :admin_privileges], [:users_manage_invites])
+ end
test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
clear_config([:instance, :registrations_open], false)
@@ -157,6 +177,21 @@ test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
end
describe "POST /api/pleroma/admin/users/invite_token" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:users_manage_invites])
+ end
+
+ test "returns 403 if not privileged with :users_manage_invites", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/users/invite_token")
+
+ assert json_response(conn, :forbidden)
+ end
+
test "without options", %{conn: conn} do
conn =
conn
@@ -221,6 +256,18 @@ test "with max use and expires_at", %{conn: conn} do
end
describe "GET /api/pleroma/admin/users/invites" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:users_manage_invites])
+ end
+
+ test "returns 403 if not privileged with :users_manage_invites", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/users/invites")
+
+ assert json_response(conn, :forbidden)
+ end
+
test "no invites", %{conn: conn} do
conn = get(conn, "/api/pleroma/admin/users/invites")
@@ -249,6 +296,21 @@ test "with invite", %{conn: conn} do
end
describe "POST /api/pleroma/admin/users/revoke_invite" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:users_manage_invites])
+ end
+
+ test "returns 403 if not privileged with :users_manage_invites", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
+
+ assert json_response(conn, :forbidden)
+ end
+
test "with token", %{conn: conn} do
{:ok, invite} = UserInviteToken.create_invite()
diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
index 6fd3fbe5a..aee26d80a 100644
--- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
- use Pleroma.Web.ConnCase, async: true
+ use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
@@ -26,6 +26,20 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do
end
describe "GET /api/pleroma/admin/reports/:id" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:reports_manage_reports])
+ end
+
+ test "returns 403 if not privileged with :reports_manage_reports", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> get("/api/pleroma/admin/reports/report_id")
+
+ assert json_response(conn, :forbidden)
+ end
+
test "returns report by its id", %{conn: conn} do
[reporter, target_user] = insert_pair(:user)
activity = insert(:note_activity, user: target_user)
@@ -89,6 +103,8 @@ test "returns 404 when report id is invalid", %{conn: conn} do
describe "PATCH /api/pleroma/admin/reports" do
setup do
+ clear_config([:instance, :admin_privileges], [:reports_manage_reports])
+
[reporter, target_user] = insert_pair(:user)
activity = insert(:note_activity, user: target_user)
@@ -112,6 +128,24 @@ test "returns 404 when report id is invalid", %{conn: conn} do
}
end
+ test "returns 403 if not privileged with :reports_manage_reports", %{
+ conn: conn,
+ id: id,
+ admin: admin
+ } do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> assign(:token, insert(:oauth_token, user: admin, scopes: ["admin:write:reports"]))
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [%{"state" => "resolved", "id" => id}]
+ })
+
+ assert json_response(conn, :forbidden)
+ end
+
test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
@@ -235,6 +269,20 @@ test "updates state of multiple reports", %{
end
describe "GET /api/pleroma/admin/reports" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:reports_manage_reports])
+ end
+
+ test "returns 403 if not privileged with :reports_manage_reports", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> get(report_path(conn, :index))
+
+ assert json_response(conn, :forbidden)
+ end
+
test "returns empty response when no reports created", %{conn: conn} do
response =
conn
@@ -343,6 +391,8 @@ test "returns 403 when requested by anonymous" do
describe "POST /api/pleroma/admin/reports/:id/notes" do
setup %{conn: conn, admin: admin} do
+ clear_config([:instance, :admin_privileges], [:reports_manage_reports])
+
[reporter, target_user] = insert_pair(:user)
activity = insert(:note_activity, user: target_user)
@@ -371,6 +421,25 @@ test "returns 403 when requested by anonymous" do
}
end
+ test "returns 403 if not privileged with :reports_manage_reports", %{
+ conn: conn,
+ report_id: report_id
+ } do
+ clear_config([:instance, :admin_privileges], [])
+
+ post_conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{
+ content: "this is disgusting2!"
+ })
+
+ delete_conn = delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/note.id")
+
+ assert json_response(post_conn, :forbidden)
+ assert json_response(delete_conn, :forbidden)
+ end
+
test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
assert [note, _] = Repo.all(ReportNote)
diff --git a/test/pleroma/web/admin_api/controllers/status_controller_test.exs b/test/pleroma/web/admin_api/controllers/status_controller_test.exs
index 8bb96ca87..8908a2812 100644
--- a/test/pleroma/web/admin_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/status_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.StatusControllerTest do
- use Pleroma.Web.ConnCase, async: true
+ use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
@@ -26,6 +26,10 @@ defmodule Pleroma.Web.AdminAPI.StatusControllerTest do
end
describe "GET /api/pleroma/admin/statuses/:id" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:messages_read])
+ end
+
test "not found", %{conn: conn} do
assert conn
|> get("/api/pleroma/admin/statuses/not_found")
@@ -50,10 +54,17 @@ test "shows activity", %{conn: conn} do
assert account["is_active"] == actor.is_active
assert account["is_confirmed"] == actor.is_confirmed
end
+
+ test "denies reading activity when not privileged", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn |> get("/api/pleroma/admin/statuses/some_id") |> json_response(:forbidden)
+ end
end
describe "PUT /api/pleroma/admin/statuses/:id" do
setup do
+ clear_config([:instance, :admin_privileges], [:messages_delete])
activity = insert(:note_activity)
%{id: activity.id}
@@ -122,10 +133,20 @@ test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
assert %{"error" => "test - Invalid value for enum."} =
json_response_and_validate_schema(conn, :bad_request)
end
+
+ test "it requires privileged role :messages_delete", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/some_id", %{})
+ |> json_response(:forbidden)
+ end
end
describe "DELETE /api/pleroma/admin/statuses/:id" do
setup do
+ clear_config([:instance, :admin_privileges], [:messages_delete])
activity = insert(:note_activity)
%{id: activity.id}
@@ -149,9 +170,22 @@ test "returns 404 when the status does not exist", %{conn: conn} do
assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"}
end
+
+ test "it requires privileged role :messages_delete", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> put_req_header("content-type", "application/json")
+ |> delete("/api/pleroma/admin/statuses/some_id")
+ |> json_response(:forbidden)
+ end
end
describe "GET /api/pleroma/admin/statuses" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:messages_read])
+ end
+
test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do
blocked = insert(:user)
user = insert(:user)
@@ -197,5 +231,13 @@ test "returns private and direct statuses with godmode on", %{conn: conn, admin:
conn = get(conn, "/api/pleroma/admin/statuses?godmode=true")
assert json_response_and_validate_schema(conn, 200) |> length() == 3
end
+
+ test "it requires privileged role :messages_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/statuses")
+
+ assert json_response(conn, :forbidden)
+ end
end
end
diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
index 79971be06..bb9dcb4aa 100644
--- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.UserControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import Mock
@@ -38,6 +38,7 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
end
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
+ clear_config([:instance, :admin_privileges], [:users_read])
clear_config([:admin_token], "password123")
user = insert(:user)
@@ -47,53 +48,10 @@ test "with valid `admin_token` query parameter, skips OAuth scopes check" do
assert json_response_and_validate_schema(conn, 200)
end
- test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
- %{admin: admin} do
- user = insert(:user)
- url = "/api/pleroma/admin/users/#{user.nickname}"
-
- good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
- good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
- good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
-
- bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
- bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
- bad_token3 = nil
-
- for good_token <- [good_token1, good_token2, good_token3] do
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, good_token)
- |> get(url)
-
- assert json_response_and_validate_schema(conn, 200)
- end
-
- for good_token <- [good_token1, good_token2, good_token3] do
- conn =
- build_conn()
- |> assign(:user, nil)
- |> assign(:token, good_token)
- |> get(url)
-
- assert json_response(conn, :forbidden)
- end
-
- for bad_token <- [bad_token1, bad_token2, bad_token3] do
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, bad_token)
- |> get(url)
-
- assert json_response_and_validate_schema(conn, :forbidden)
- end
- end
-
describe "DELETE /api/pleroma/admin/users" do
test "single user", %{admin: admin, conn: conn} do
clear_config([:instance, :federating], true)
+ clear_config([:instance, :admin_privileges], [:users_delete])
user =
insert(:user,
@@ -149,6 +107,8 @@ test "single user", %{admin: admin, conn: conn} do
end
test "multiple users", %{admin: admin, conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_delete])
+
user_one = insert(:user)
user_two = insert(:user)
@@ -168,6 +128,17 @@ test "multiple users", %{admin: admin, conn: conn} do
assert response -- [user_one.nickname, user_two.nickname] == []
end
+
+ test "Needs privileged role", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users?nickname=nickname")
+
+ assert json_response(response, :forbidden)
+ end
end
describe "/api/pleroma/admin/users" do
@@ -307,7 +278,19 @@ test "Multiple user creation works in transaction", %{conn: conn} do
end
end
- describe "/api/pleroma/admin/users/:nickname" do
+ describe "GET /api/pleroma/admin/users/:nickname" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:users_read])
+ end
+
+ test "returns 403 if not privileged with :users_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/users/user.nickname")
+
+ assert json_response(conn, :forbidden)
+ end
+
test "Show", %{conn: conn} do
user = insert(:user)
@@ -323,6 +306,50 @@ test "when the user doesn't exist", %{conn: conn} do
assert %{"error" => "Not found"} == json_response_and_validate_schema(conn, 404)
end
+
+ test "requires admin:read:accounts or broader scope",
+ %{admin: admin} do
+ user = insert(:user)
+ url = "/api/pleroma/admin/users/#{user.nickname}"
+
+ good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
+ good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+ good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
+
+ bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
+ bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
+ bad_token3 = nil
+
+ for good_token <- [good_token1, good_token2, good_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response_and_validate_schema(conn, 200)
+ end
+
+ for good_token <- [good_token1, good_token2, good_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, nil)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+
+ for bad_token <- [bad_token1, bad_token2, bad_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, bad_token)
+ |> get(url)
+
+ assert json_response_and_validate_schema(conn, :forbidden)
+ end
+ end
end
describe "/api/pleroma/admin/users/follow" do
@@ -378,6 +405,18 @@ test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
end
describe "GET /api/pleroma/admin/users" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:users_read])
+ end
+
+ test "returns 403 if not privileged with :users_read", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn = get(conn, "/api/pleroma/admin/users?page=1")
+
+ assert json_response(conn, :forbidden)
+ end
+
test "renders users array for the first page", %{conn: conn, admin: admin} do
user = insert(:user, local: false, tags: ["foo", "bar"])
user2 = insert(:user, is_approved: false, registration_reason: "I'm a chill dude")
@@ -810,49 +849,9 @@ test "it omits relay user", %{admin: admin, conn: conn} do
end
end
- test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
- user_one = insert(:user, is_active: false)
- user_two = insert(:user, is_active: false)
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> patch(
- "/api/pleroma/admin/users/activate",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response_and_validate_schema(conn, 200)
- assert Enum.map(response["users"], & &1["is_active"]) == [true, true]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
- test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
- user_one = insert(:user, is_active: true)
- user_two = insert(:user, is_active: true)
-
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> patch(
- "/api/pleroma/admin/users/deactivate",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response_and_validate_schema(conn, 200)
- assert Enum.map(response["users"], & &1["is_active"]) == [false, false]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_invites])
+
user_one = insert(:user, is_approved: false)
user_two = insert(:user, is_approved: false)
@@ -873,6 +872,21 @@ test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
"@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
end
+ test "PATCH /api/pleroma/admin/users/approve returns 403 if not privileged with :users_manage_invites",
+ %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch(
+ "/api/pleroma/admin/users/approve",
+ %{nicknames: ["user_one.nickname", "user_two.nickname"]}
+ )
+
+ assert json_response(conn, :forbidden)
+ end
+
test "PATCH /api/pleroma/admin/users/suggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: false)
user2 = insert(:user, is_suggested: false)
@@ -923,24 +937,113 @@ test "PATCH /api/pleroma/admin/users/unsuggest", %{admin: admin, conn: conn} do
"@#{admin.nickname} removed suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
- test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
- user = insert(:user)
+ describe "user activation" do
+ test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
- conn =
- conn
- |> put_req_header("content-type", "application/json")
- |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
+ user_one = insert(:user, is_active: false)
+ user_two = insert(:user, is_active: false)
- assert json_response_and_validate_schema(conn, 200) ==
- user_response(
- user,
- %{"is_active" => !user.is_active}
- )
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch(
+ "/api/pleroma/admin/users/activate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
- log_entry = Repo.one(ModerationLog)
+ response = json_response_and_validate_schema(conn, 200)
+ assert Enum.map(response["users"], & &1["is_active"]) == [true, true]
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated users: @#{user.nickname}"
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
+
+ user_one = insert(:user, is_active: true)
+ user_two = insert(:user, is_active: true)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch(
+ "/api/pleroma/admin/users/deactivate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response_and_validate_schema(conn, 200)
+ assert Enum.map(response["users"], & &1["is_active"]) == [false, false]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
+
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
+
+ assert json_response_and_validate_schema(conn, 200) ==
+ user_response(
+ user,
+ %{"is_active" => !user.is_active}
+ )
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user.nickname}"
+ end
+
+ test "it requires privileged role :statuses_activation to activate", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch(
+ "/api/pleroma/admin/users/activate",
+ %{nicknames: ["user_one.nickname", "user_two.nickname"]}
+ )
+
+ assert json_response(conn, :forbidden)
+ end
+
+ test "it requires privileged role :statuses_activation to deactivate", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch(
+ "/api/pleroma/admin/users/deactivate",
+ %{nicknames: ["user_one.nickname", "user_two.nickname"]}
+ )
+
+ assert json_response(conn, :forbidden)
+ end
+
+ test "it requires privileged role :statuses_activation to toggle activation", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> patch("/api/pleroma/admin/users/user.nickname/toggle_activation")
+
+ assert json_response(conn, :forbidden)
+ end
end
defp user_response(user, attrs \\ %{}) do
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
index 9d52bd93e..44355c26d 100644
--- a/test/pleroma/web/common_api_test.exs
+++ b/test/pleroma/web/common_api_test.exs
@@ -4,7 +4,7 @@
defmodule Pleroma.Web.CommonAPITest do
use Oban.Testing, repo: Pleroma.Repo
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
alias Pleroma.Activity
alias Pleroma.Chat
@@ -331,7 +331,7 @@ test "it allows users to delete their posts" do
refute Activity.get_by_id(post.id)
end
- test "it does not allow a user to delete their posts" do
+ test "it does not allow a user to delete posts from another user" do
user = insert(:user)
other_user = insert(:user)
@@ -341,7 +341,8 @@ test "it does not allow a user to delete their posts" do
assert Activity.get_by_id(post.id)
end
- test "it allows moderators to delete other user's posts" do
+ test "it allows privileged users to delete other user's posts" do
+ clear_config([:instance, :moderator_privileges], [:messages_delete])
user = insert(:user)
moderator = insert(:user, is_moderator: true)
@@ -353,19 +354,20 @@ test "it allows moderators to delete other user's posts" do
refute Activity.get_by_id(post.id)
end
- test "it allows admins to delete other user's posts" do
+ test "it doesn't allow unprivileged mods or admins to delete other user's posts" do
+ clear_config([:instance, :admin_privileges], [])
+ clear_config([:instance, :moderator_privileges], [])
user = insert(:user)
- moderator = insert(:user, is_admin: true)
+ moderator = insert(:user, is_moderator: true, is_admin: true)
{:ok, post} = CommonAPI.post(user, %{status: "namu amida butsu"})
- assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
- assert delete.local
-
- refute Activity.get_by_id(post.id)
+ assert {:error, "Could not delete"} = CommonAPI.delete(post.id, moderator)
+ assert Activity.get_by_id(post.id)
end
- test "superusers deleting non-local posts won't federate the delete" do
+ test "privileged users deleting non-local posts won't federate the delete" do
+ clear_config([:instance, :admin_privileges], [:messages_delete])
# This is the user of the ingested activity
_user =
insert(:user,
@@ -374,7 +376,7 @@ test "superusers deleting non-local posts won't federate the delete" do
last_refreshed_at: NaiveDateTime.utc_now()
)
- moderator = insert(:user, is_admin: true)
+ admin = insert(:user, is_admin: true)
data =
File.read!("test/fixtures/mastodon-post-activity.json")
@@ -384,7 +386,7 @@ test "superusers deleting non-local posts won't federate the delete" do
with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end do
- assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
+ assert {:ok, delete} = CommonAPI.delete(post.id, admin)
assert delete.local
refute called(Pleroma.Web.Federator.publish(:_))
end
diff --git a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
index 403628488..1524df98f 100644
--- a/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/notification_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
alias Pleroma.Notification
alias Pleroma.Repo
@@ -74,12 +74,15 @@ test "by default, does not contain pleroma:chat_mention" do
end
test "by default, does not contain pleroma:report" do
- %{user: user, conn: conn} = oauth_access(["read:notifications"])
+ clear_config([:instance, :moderator_privileges], [:reports_manage_reports])
+
+ user = insert(:user)
other_user = insert(:user)
third_user = insert(:user)
- user
- |> User.admin_api_update(%{is_moderator: true})
+ {:ok, user} = user |> User.admin_api_update(%{is_moderator: true})
+
+ %{conn: conn} = oauth_access(["read:notifications"], user: user)
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
@@ -101,6 +104,39 @@ test "by default, does not contain pleroma:report" do
assert [_] = result
end
+ test "Pleroma:report is hidden for non-privileged users" do
+ clear_config([:instance, :moderator_privileges], [:reports_manage_reports])
+
+ user = insert(:user)
+ other_user = insert(:user)
+ third_user = insert(:user)
+
+ {:ok, user} = user |> User.admin_api_update(%{is_moderator: true})
+
+ %{conn: conn} = oauth_access(["read:notifications"], user: user)
+
+ {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
+
+ {:ok, _report} =
+ CommonAPI.report(third_user, %{account_id: other_user.id, status_ids: [activity.id]})
+
+ result =
+ conn
+ |> get("/api/v1/notifications?include_types[]=pleroma:report")
+ |> json_response_and_validate_schema(200)
+
+ assert [_] = result
+
+ clear_config([:instance, :moderator_privileges], [])
+
+ result =
+ conn
+ |> get("/api/v1/notifications?include_types[]=pleroma:report")
+ |> json_response_and_validate_schema(200)
+
+ assert [] == result
+ end
+
test "excludes mentions from blockers when blockers_visible is false" do
clear_config([:activitypub, :blockers_visible], false)
diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
index dbb840574..5bae2cd00 100644
--- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
+++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
use Oban.Testing, repo: Pleroma.Repo
alias Pleroma.Activity
@@ -971,25 +971,8 @@ test "when you didn't create it" do
assert Activity.get_by_id(activity.id) == activity
end
- test "when you're an admin", %{conn: conn} do
- activity = insert(:note_activity)
- user = insert(:user, is_admin: true)
-
- res_conn =
- conn
- |> assign(:user, user)
- |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:statuses"]))
- |> delete("/api/v1/statuses/#{activity.id}")
-
- assert %{} = json_response_and_validate_schema(res_conn, 200)
-
- assert ModerationLog |> Repo.one() |> ModerationLog.get_log_entry_message() ==
- "@#{user.nickname} deleted status ##{activity.id}"
-
- refute Activity.get_by_id(activity.id)
- end
-
- test "when you're a moderator", %{conn: conn} do
+ test "when you're privileged to", %{conn: conn} do
+ clear_config([:instance, :moderator_privileges], [:messages_delete])
activity = insert(:note_activity)
user = insert(:user, is_moderator: true)
diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs
index 692ec8c92..3bb4970ca 100644
--- a/test/pleroma/web/mastodon_api/views/account_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.AccountViewTest do
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
alias Pleroma.User
alias Pleroma.UserRelationship
@@ -84,6 +84,7 @@ test "Represent a user account" do
tags: [],
is_admin: false,
is_moderator: false,
+ privileges: [],
is_suggested: false,
hide_favorites: true,
hide_followers: false,
@@ -99,6 +100,147 @@ test "Represent a user account" do
assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true})
end
+ describe "roles and privileges" do
+ setup do
+ clear_config([:instance, :moderator_privileges], [:cofe, :only_moderator])
+ clear_config([:instance, :admin_privileges], [:cofe, :only_admin])
+
+ %{
+ user: insert(:user),
+ moderator: insert(:user, is_moderator: true),
+ admin: insert(:user, is_admin: true),
+ moderator_admin: insert(:user, is_moderator: true, is_admin: true),
+ user_no_show_roles: insert(:user, show_role: false),
+ moderator_admin_no_show_roles:
+ insert(:user, is_moderator: true, is_admin: true, show_role: false)
+ }
+ end
+
+ test "shows roles and privileges when show_role: true", %{
+ user: user,
+ moderator: moderator,
+ admin: admin,
+ moderator_admin: moderator_admin,
+ user_no_show_roles: user_no_show_roles,
+ moderator_admin_no_show_roles: moderator_admin_no_show_roles
+ } do
+ assert %{pleroma: %{is_moderator: false, is_admin: false}} =
+ AccountView.render("show.json", %{user: user, skip_visibility_check: true})
+
+ assert [] ==
+ AccountView.render("show.json", %{user: user, skip_visibility_check: true})[
+ :pleroma
+ ][:privileges]
+ |> Enum.sort()
+
+ assert %{pleroma: %{is_moderator: true, is_admin: false}} =
+ AccountView.render("show.json", %{user: moderator, skip_visibility_check: true})
+
+ assert [:cofe, :only_moderator] ==
+ AccountView.render("show.json", %{user: moderator, skip_visibility_check: true})[
+ :pleroma
+ ][:privileges]
+ |> Enum.sort()
+
+ assert %{pleroma: %{is_moderator: false, is_admin: true}} =
+ AccountView.render("show.json", %{user: admin, skip_visibility_check: true})
+
+ assert [:cofe, :only_admin] ==
+ AccountView.render("show.json", %{user: admin, skip_visibility_check: true})[
+ :pleroma
+ ][:privileges]
+ |> Enum.sort()
+
+ assert %{pleroma: %{is_moderator: true, is_admin: true}} =
+ AccountView.render("show.json", %{
+ user: moderator_admin,
+ skip_visibility_check: true
+ })
+
+ assert [:cofe, :only_admin, :only_moderator] ==
+ AccountView.render("show.json", %{
+ user: moderator_admin,
+ skip_visibility_check: true
+ })[:pleroma][:privileges]
+ |> Enum.sort()
+
+ refute match?(
+ %{pleroma: %{is_moderator: _}},
+ AccountView.render("show.json", %{
+ user: user_no_show_roles,
+ skip_visibility_check: true
+ })
+ )
+
+ refute match?(
+ %{pleroma: %{is_admin: _}},
+ AccountView.render("show.json", %{
+ user: user_no_show_roles,
+ skip_visibility_check: true
+ })
+ )
+
+ refute match?(
+ %{pleroma: %{privileges: _}},
+ AccountView.render("show.json", %{
+ user: user_no_show_roles,
+ skip_visibility_check: true
+ })
+ )
+
+ refute match?(
+ %{pleroma: %{is_moderator: _}},
+ AccountView.render("show.json", %{
+ user: moderator_admin_no_show_roles,
+ skip_visibility_check: true
+ })
+ )
+
+ refute match?(
+ %{pleroma: %{is_admin: _}},
+ AccountView.render("show.json", %{
+ user: moderator_admin_no_show_roles,
+ skip_visibility_check: true
+ })
+ )
+
+ refute match?(
+ %{pleroma: %{privileges: _}},
+ AccountView.render("show.json", %{
+ user: moderator_admin_no_show_roles,
+ skip_visibility_check: true
+ })
+ )
+ end
+
+ test "shows roles and privileges when viewing own account, even when show_role: false", %{
+ user_no_show_roles: user_no_show_roles,
+ moderator_admin_no_show_roles: moderator_admin_no_show_roles
+ } do
+ assert %{pleroma: %{is_moderator: false, is_admin: false, privileges: []}} =
+ AccountView.render("show.json", %{
+ user: user_no_show_roles,
+ skip_visibility_check: true,
+ for: user_no_show_roles
+ })
+
+ assert %{
+ pleroma: %{
+ is_moderator: true,
+ is_admin: true,
+ privileges: privileges
+ }
+ } =
+ AccountView.render("show.json", %{
+ user: moderator_admin_no_show_roles,
+ skip_visibility_check: true,
+ for: moderator_admin_no_show_roles
+ })
+
+ assert [:cofe, :only_admin, :only_moderator] == privileges |> Enum.sort()
+ end
+ end
+
describe "favicon" do
setup do
[user: insert(:user)]
@@ -186,6 +328,7 @@ test "Represent a Service(bot) account" do
tags: [],
is_admin: false,
is_moderator: false,
+ privileges: [],
is_suggested: false,
hide_favorites: true,
hide_followers: false,
@@ -214,8 +357,10 @@ test "Represent a Funkwhale channel" do
assert represented.url == "https://channels.tests.funkwhale.audio/channels/compositions"
end
- test "Represent a deactivated user for an admin" do
- admin = insert(:user, is_admin: true)
+ test "Represent a deactivated user for a privileged user" do
+ clear_config([:instance, :moderator_privileges], [:users_manage_activation_state])
+
+ admin = insert(:user, is_moderator: true)
deactivated_user = insert(:user, is_active: false)
represented = AccountView.render("show.json", %{user: deactivated_user, for: admin})
assert represented[:pleroma][:deactivated] == true
diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs
index d3d74f5cd..6ea894691 100644
--- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs
+++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do
- use Pleroma.DataCase
+ use Pleroma.DataCase, async: false
alias Pleroma.Activity
alias Pleroma.Chat
@@ -218,9 +218,11 @@ test "Poll notification" do
end
test "Report notification" do
+ clear_config([:instance, :moderator_privileges], [:reports_manage_reports])
+
reporting_user = insert(:user)
reported_user = insert(:user)
- {:ok, moderator_user} = insert(:user) |> User.admin_api_update(%{is_moderator: true})
+ moderator_user = insert(:user, is_moderator: true)
{:ok, activity} = CommonAPI.report(reporting_user, %{account_id: reported_user.id})
{:ok, [notification]} = Notification.create_notifications(activity)
diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs
index 247ad7501..f474220be 100644
--- a/test/pleroma/web/node_info_test.exs
+++ b/test/pleroma/web/node_info_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.NodeInfoTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
import Pleroma.Factory
@@ -40,6 +40,19 @@ test "nodeinfo shows staff accounts", %{conn: conn} do
assert admin.ap_id in result["metadata"]["staffAccounts"]
end
+ test "nodeinfo shows roles and privileges", %{conn: conn} do
+ clear_config([:instance, :moderator_privileges], [:cofe])
+ clear_config([:instance, :admin_privileges], [:suya, :cofe])
+
+ conn =
+ conn
+ |> get("/nodeinfo/2.1.json")
+
+ assert result = json_response(conn, 200)
+
+ assert %{"admin" => ["suya", "cofe"], "moderator" => ["cofe"]} == result["metadata"]["roles"]
+ end
+
test "nodeinfo shows restricted nicknames", %{conn: conn} do
conn =
conn
diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs
index 200ce3b68..540b452c7 100644
--- a/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs
+++ b/test/pleroma/web/pleroma_api/controllers/emoji_file_controller_test.exs
@@ -3,7 +3,7 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do
- use Pleroma.Web.ConnCase
+ use Pleroma.Web.ConnCase, async: false
import Mock
import Tesla.Mock
@@ -30,6 +30,7 @@ defmodule Pleroma.Web.PleromaAPI.EmojiFileControllerTest do
describe "POST/PATCH/DELETE /api/pleroma/emoji/packs/files?name=:name" do
setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
pack_file = "#{@emoji_path}/test_pack/pack.json"
original_content = File.read!(pack_file)
@@ -377,5 +378,32 @@ test "update with empty shortcode", %{admin_conn: admin_conn} do
})
|> json_response_and_validate_schema(:bad_request)
end
+
+ test "it requires privileged role :emoji_manage_emoji", %{admin_conn: admin_conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/files?name=test_pack", %{
+ file: %Plug.Upload{
+ filename: "shortcode.png",
+ path: "#{Pleroma.Config.get([:instance, :static_dir])}/add/shortcode.png"
+ }
+ })
+ |> json_response(:forbidden)
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> patch("/api/pleroma/emoji/packs/files?name=test_pack", %{
+ shortcode: "blank",
+ new_filename: "dir_2/blank_3.png"
+ })
+ |> json_response(:forbidden)
+
+ assert admin_conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> delete("/api/pleroma/emoji/packs/files?name=test_pack&shortcode=blank3")
+ |> json_response(:forbidden)
+ end
end
end
diff --git a/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs
index d1fd1cbb0..1d5240639 100644
--- a/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs
+++ b/test/pleroma/web/pleroma_api/controllers/emoji_pack_controller_test.exs
@@ -99,6 +99,10 @@ test "GET /api/pleroma/emoji/packs", %{conn: conn} do
end
describe "GET /api/pleroma/emoji/packs/remote" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
+ end
+
test "shareable instance", %{admin_conn: admin_conn, conn: conn} do
resp =
conn
@@ -136,6 +140,14 @@ test "non shareable instance", %{admin_conn: admin_conn} do
"error" => "The requested instance does not support sharing emoji packs"
}
end
+
+ test "it requires privileged role :emoji_manage_emoji", %{admin_conn: admin_conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert admin_conn
+ |> get("/api/pleroma/emoji/packs/remote?url=https://example.com")
+ |> json_response(:forbidden)
+ end
end
describe "GET /api/pleroma/emoji/packs/archive?name=:name" do
@@ -170,6 +182,10 @@ test "non downloadable pack", %{conn: conn} do
end
describe "POST /api/pleroma/emoji/packs/download" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
+ end
+
test "shared pack from remote and non shared from fallback-src", %{
admin_conn: admin_conn,
conn: conn
@@ -344,10 +360,24 @@ test "other error", %{admin_conn: admin_conn} do
"The pack was not set as shared and there is no fallback src to download from"
}
end
+
+ test "it requires privileged role :emoji_manage_emoji", %{admin_conn: conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> post("/api/pleroma/emoji/packs/download", %{
+ url: "https://example.com",
+ name: "test_pack",
+ as: "test_pack2"
+ })
+ |> json_response(:forbidden)
+ end
end
describe "PATCH/update /api/pleroma/emoji/pack?name=:name" do
setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
pack_file = "#{@emoji_path}/test_pack/pack.json"
original_content = File.read!(pack_file)
@@ -435,9 +465,25 @@ test "when the fallback source doesn't have all the files", ctx do
"error" => "The fallback archive does not have all files specified in pack.json"
}
end
+
+ test "it requires privileged role :emoji_manage_emoji", %{
+ admin_conn: conn,
+ new_data: new_data
+ } do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert conn
+ |> put_req_header("content-type", "multipart/form-data")
+ |> patch("/api/pleroma/emoji/pack?name=test_pack", %{metadata: new_data})
+ |> json_response(:forbidden)
+ end
end
describe "POST/DELETE /api/pleroma/emoji/pack?name=:name" do
+ setup do
+ clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
+ end
+
test "returns an error on creates pack when file system not writable", %{
admin_conn: admin_conn
} do
@@ -520,6 +566,18 @@ test "with empty name", %{admin_conn: admin_conn} do
"error" => "pack name cannot be empty"
}
end
+
+ test "it requires privileged role :emoji_manage_emoji", %{admin_conn: admin_conn} do
+ clear_config([:instance, :admin_privileges], [])
+
+ assert admin_conn
+ |> post("/api/pleroma/emoji/pack?name= ")
+ |> json_response(:forbidden)
+
+ assert admin_conn
+ |> delete("/api/pleroma/emoji/pack?name= ")
+ |> json_response(:forbidden)
+ end
end
test "deleting nonexisting pack", %{admin_conn: admin_conn} do
@@ -578,6 +636,12 @@ test "filesystem import", %{admin_conn: admin_conn, conn: conn} do
"blank2" => "blank.png",
"foo" => "blank.png"
}
+
+ clear_config([:instance, :admin_privileges], [])
+
+ assert admin_conn
+ |> get("/api/pleroma/emoji/packs/import")
+ |> json_response(:forbidden)
end
describe "GET /api/pleroma/emoji/pack?name=:name" do
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..4b6679b66
--- /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 privileged role even if other role isn't privileged" 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)
+
+ # privileged 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)
+
+ # privileged 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
diff --git a/test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs b/test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs
deleted file mode 100644
index c684714b8..000000000
--- a/test/pleroma/web/plugs/ensure_staff_privileged_plug_test.exs
+++ /dev/null
@@ -1,60 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2022 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