diff options
34 files changed, 849 insertions, 260 deletions
diff --git a/changelog.d/instance-rules.add b/changelog.d/instance-rules.add new file mode 100644 index 000000000..42f3cbfa1 --- /dev/null +++ b/changelog.d/instance-rules.add @@ -0,0 +1 @@ +Add instance rules
\ No newline at end of file diff --git a/changelog.d/missing-mrfs.add b/changelog.d/missing-mrfs.add new file mode 100644 index 000000000..6a17f9e1a --- /dev/null +++ b/changelog.d/missing-mrfs.add @@ -0,0 +1 @@ +Startup detection for configured MRF modules that are missing or incorrectly defined diff --git a/changelog.d/web_push_filtered.fix b/changelog.d/web_push_filtered.fix new file mode 100644 index 000000000..b9159362a --- /dev/null +++ b/changelog.d/web_push_filtered.fix @@ -0,0 +1 @@ +Web Push notifications are no longer generated for muted/blocked threads and users. diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index 182a760fa..5b373b8e1 100644 --- a/docs/development/API/admin_api.md +++ b/docs/development/API/admin_api.md @@ -1751,3 +1751,53 @@ Note that this differs from the Mastodon API variant: Mastodon API only returns  ```json  {}  ``` + + +## `GET /api/v1/pleroma/admin/rules` + +### List rules + +- Response: JSON, list of rules + +```json +[ +  { +    "id": "1", +    "priority": 1, +    "text": "There are no rules", +    "hint": null +  } +] +``` + +## `POST /api/v1/pleroma/admin/rules` + +### Create a rule + +- Params: +  - `text`: string, required, rule content +  - `hint`: string, optional, rule description +  - `priority`: integer, optional, rule ordering priority + +- Response: JSON, a single rule + +## `PATCH /api/v1/pleroma/admin/rules/:id` + +### Update a rule + +- Params: +  - `text`: string, optional, rule content +  - `hint`: string, optional, rule description +  - `priority`: integer, optional, rule ordering priority + +- Response: JSON, a single rule + +## `DELETE /api/v1/pleroma/admin/rules/:id` + +### Delete a rule + +- Response: JSON, empty object + +```json +{} +``` diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex index 819245481..8c0df64fc 100644 --- a/lib/pleroma/application_requirements.ex +++ b/lib/pleroma/application_requirements.ex @@ -28,6 +28,7 @@ defmodule Pleroma.ApplicationRequirements do      |> check_welcome_message_config!()      |> check_rum!()      |> check_repo_pool_size!() +    |> check_mrfs()      |> handle_result()    end @@ -234,4 +235,25 @@ defmodule Pleroma.ApplicationRequirements do        true      end    end + +  defp check_mrfs(:ok) do +    mrfs = Config.get!([:mrf, :policies]) + +    missing_mrfs = +      Enum.reduce(mrfs, [], fn x, acc -> +        if Code.ensure_compiled(x) do +          acc +        else +          acc ++ [x] +        end +      end) + +    if Enum.empty?(missing_mrfs) do +      :ok +    else +      {:error, "The following MRF modules are configured but missing: #{inspect(missing_mrfs)}"} +    end +  end + +  defp check_mrfs(result), do: result  end diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index d814b4931..3a5e35301 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -19,7 +19,8 @@ defmodule Pleroma.Constants do        "context_id",        "deleted_activity_id",        "pleroma_internal", -      "generator" +      "generator", +      "rules"      ]    ) diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 710b19866..a80279fa6 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -361,36 +361,32 @@ defmodule Pleroma.Notification do      end    end -  @spec create_notifications(Activity.t(), keyword()) :: {:ok, [Notification.t()] | []} -  def create_notifications(activity, options \\ []) +  @spec create_notifications(Activity.t()) :: {:ok, [Notification.t()] | []} +  def create_notifications(activity) -  def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity, options) do +  def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = activity) do      object = Object.normalize(activity, fetch: false)      if object && object.data["type"] == "Answer" do        {:ok, []}      else -      do_create_notifications(activity, options) +      do_create_notifications(activity)      end    end -  def create_notifications(%Activity{data: %{"type" => type}} = activity, options) +  def create_notifications(%Activity{data: %{"type" => type}} = activity)        when type in ["Follow", "Like", "Announce", "Move", "EmojiReact", "Flag", "Update"] do -    do_create_notifications(activity, options) +    do_create_notifications(activity)    end -  def create_notifications(_, _), do: {:ok, []} +  def create_notifications(_), do: {:ok, []} -  defp do_create_notifications(%Activity{} = activity, options) do -    do_send = Keyword.get(options, :do_send, true) - -    {enabled_receivers, disabled_receivers} = get_notified_from_activity(activity) -    potential_receivers = enabled_receivers ++ disabled_receivers +  defp do_create_notifications(%Activity{} = activity) do +    enabled_receivers = get_notified_from_activity(activity)      notifications = -      Enum.map(potential_receivers, fn user -> -        do_send = do_send && user in enabled_receivers -        create_notification(activity, user, do_send: do_send) +      Enum.map(enabled_receivers, fn user -> +        create_notification(activity, user)        end)        |> Enum.reject(&is_nil/1) @@ -450,7 +446,6 @@ defmodule Pleroma.Notification do    # TODO move to sql, too.    def create_notification(%Activity{} = activity, %User{} = user, opts \\ []) do -    do_send = Keyword.get(opts, :do_send, true)      type = Keyword.get(opts, :type, type_from_activity(activity))      unless skip?(activity, user, opts) do @@ -465,11 +460,6 @@ defmodule Pleroma.Notification do          |> Marker.multi_set_last_read_id(user, "notifications")          |> Repo.transaction() -      if do_send do -        Streamer.stream(["user", "user:notification"], notification) -        Push.send(notification) -      end -        notification      end    end @@ -527,10 +517,7 @@ defmodule Pleroma.Notification do        |> exclude_relationship_restricted_ap_ids(activity)        |> exclude_thread_muter_ap_ids(activity) -    notification_enabled_users = -      Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end) - -    {notification_enabled_users, potential_receivers -- notification_enabled_users} +    Enum.filter(potential_receivers, fn u -> u.ap_id in notification_enabled_ap_ids end)    end    def get_notified_from_activity(_, _local_only), do: {[], []} @@ -643,6 +630,7 @@ defmodule Pleroma.Notification do    def skip?(%Activity{} = activity, %User{} = user, opts) do      [        :self, +      :internal,        :invisible,        :block_from_strangers,        :recently_followed, @@ -662,6 +650,12 @@ defmodule Pleroma.Notification do      end    end +  def skip?(:internal, %Activity{} = activity, _user, _opts) do +    actor = activity.data["actor"] +    user = User.get_cached_by_ap_id(actor) +    User.internal?(user) +  end +    def skip?(:invisible, %Activity{} = activity, _user, _opts) do      actor = activity.data["actor"]      user = User.get_cached_by_ap_id(actor) @@ -748,4 +742,12 @@ defmodule Pleroma.Notification do      )      |> Repo.update_all(set: [seen: true])    end + +  @spec send(list(Notification.t())) :: :ok +  def send(notifications) do +    Enum.each(notifications, fn notification -> +      Streamer.stream(["user", "user:notification"], notification) +      Push.send(notification) +    end) +  end  end diff --git a/lib/pleroma/rule.ex b/lib/pleroma/rule.ex new file mode 100644 index 000000000..3ba413214 --- /dev/null +++ b/lib/pleroma/rule.ex @@ -0,0 +1,68 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Rule do +  use Ecto.Schema + +  import Ecto.Changeset +  import Ecto.Query + +  alias Pleroma.Repo +  alias Pleroma.Rule + +  schema "rules" do +    field(:priority, :integer, default: 0) +    field(:text, :string) +    field(:hint, :string) + +    timestamps() +  end + +  def changeset(%Rule{} = rule, params \\ %{}) do +    rule +    |> cast(params, [:priority, :text, :hint]) +    |> validate_required([:text]) +  end + +  def query do +    Rule +    |> order_by(asc: :priority) +    |> order_by(asc: :id) +  end + +  def get(ids) when is_list(ids) do +    from(r in __MODULE__, where: r.id in ^ids) +    |> Repo.all() +  end + +  def get(id), do: Repo.get(__MODULE__, id) + +  def exists?(id) do +    from(r in __MODULE__, where: r.id == ^id) +    |> Repo.exists?() +  end + +  def create(params) do +    {:ok, rule} = +      %Rule{} +      |> changeset(params) +      |> Repo.insert() + +    rule +  end + +  def update(params, id) do +    {:ok, rule} = +      get(id) +      |> changeset(params) +      |> Repo.update() + +    rule +  end + +  def delete(id) do +    get(id) +    |> Repo.delete() +  end +end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index a1fccc705..643877268 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -200,7 +200,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    end    def notify_and_stream(activity) do -    Notification.create_notifications(activity) +    {:ok, notifications} = Notification.create_notifications(activity) +    Notification.send(notifications)      original_activity =        case activity do @@ -1259,6 +1260,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do    defp restrict_quote_url(query, _), do: query +  defp restrict_rule(query, %{rule_id: rule_id}) do +    from( +      activity in query, +      where: fragment("(?)->'rules' \\? (?)", activity.data, ^rule_id) +    ) +  end + +  defp restrict_rule(query, _), do: query +    defp exclude_poll_votes(query, %{include_poll_votes: true}), do: query    defp exclude_poll_votes(query, _) do @@ -1421,6 +1431,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        |> restrict_instance(opts)        |> restrict_announce_object_actor(opts)        |> restrict_filtered(opts) +      |> restrict_rule(opts)        |> restrict_quote_url(opts)        |> maybe_restrict_deactivated_users(opts)        |> exclude_poll_votes(opts) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 7421b8ed8..60b4d5f1b 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -21,7 +21,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Utils -  alias Pleroma.Web.Push    alias Pleroma.Web.Streamer    alias Pleroma.Workers.PollWorker @@ -125,7 +124,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do          nil      end -    {:ok, notifications} = Notification.create_notifications(object, do_send: false) +    {:ok, notifications} = Notification.create_notifications(object)      meta =        meta @@ -184,7 +183,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      liked_object = Object.get_by_ap_id(object.data["object"])      Utils.add_like_to_object(object, liked_object) -    Notification.create_notifications(object) +    {:ok, notifications} = Notification.create_notifications(object) + +    meta = +      meta +      |> add_notifications(notifications)      {:ok, object, meta}    end @@ -202,7 +205,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    def handle(%{data: %{"type" => "Create"}} = activity, meta) do      with {:ok, object, meta} <- handle_object_creation(meta[:object_data], activity, meta),           %User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do -      {:ok, notifications} = Notification.create_notifications(activity, do_send: false) +      {:ok, notifications} = Notification.create_notifications(activity)        {:ok, _user} = ActivityPub.increase_note_count_if_public(user, object)        {:ok, _user} = ActivityPub.update_last_status_at_if_public(user, object) @@ -256,11 +259,13 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      Utils.add_announce_to_object(object, announced_object) -    if !User.internal?(user) do -      Notification.create_notifications(object) +    {:ok, notifications} = Notification.create_notifications(object) -      ap_streamer().stream_out(object) -    end +    if !User.internal?(user), do: ap_streamer().stream_out(object) + +    meta = +      meta +      |> add_notifications(notifications)      {:ok, object, meta}    end @@ -281,7 +286,11 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      reacted_object = Object.get_by_ap_id(object.data["object"])      Utils.add_emoji_reaction_to_object(object, reacted_object) -    Notification.create_notifications(object) +    {:ok, notifications} = Notification.create_notifications(object) + +    meta = +      meta +      |> add_notifications(notifications)      {:ok, object, meta}    end @@ -585,10 +594,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    defp send_notifications(meta) do      Keyword.get(meta, :notifications, []) -    |> Enum.each(fn notification -> -      Streamer.stream(["user", "user:notification"], notification) -      Push.send(notification) -    end) +    |> Notification.send()      meta    end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 52cb64fc5..797e79dda 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -721,14 +721,18 @@ defmodule Pleroma.Web.ActivityPub.Utils do    #### Flag-related helpers    @spec make_flag_data(map(), map()) :: map() -  def make_flag_data(%{actor: actor, context: context, content: content} = params, additional) do +  def make_flag_data( +        %{actor: actor, context: context, content: content} = params, +        additional +      ) do      %{        "type" => "Flag",        "actor" => actor.ap_id,        "content" => content,        "object" => build_flag_object(params),        "context" => context, -      "state" => "open" +      "state" => "open", +      "rules" => Map.get(params, :rules, nil)      }      |> Map.merge(additional)    end diff --git a/lib/pleroma/web/admin_api/controllers/rule_controller.ex b/lib/pleroma/web/admin_api/controllers/rule_controller.ex new file mode 100644 index 000000000..43b2f209a --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/rule_controller.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleController do +  use Pleroma.Web, :controller + +  alias Pleroma.Repo +  alias Pleroma.Rule +  alias Pleroma.Web.Plugs.OAuthScopesPlug + +  import Pleroma.Web.ControllerHelper, +    only: [ +      json_response: 3 +    ] + +  plug(Pleroma.Web.ApiSpec.CastAndValidate) + +  plug( +    OAuthScopesPlug, +    %{scopes: ["admin:write"]} +    when action in [:create, :update, :delete] +  ) + +  plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action == :index) + +  action_fallback(AdminAPI.FallbackController) + +  defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.RuleOperation + +  def index(conn, _) do +    rules = +      Rule.query() +      |> Repo.all() + +    render(conn, "index.json", rules: rules) +  end + +  def create(%{body_params: params} = conn, _) do +    rule = +      params +      |> Rule.create() + +    render(conn, "show.json", rule: rule) +  end + +  def update(%{body_params: params} = conn, %{id: id}) do +    rule = +      params +      |> Rule.update(id) + +    render(conn, "show.json", rule: rule) +  end + +  def delete(conn, %{id: id}) do +    with {:ok, _} <- Rule.delete(id) do +      json(conn, %{}) +    else +      _ -> json_response(conn, :bad_request, "") +    end +  end +end diff --git a/lib/pleroma/web/admin_api/views/report_view.ex b/lib/pleroma/web/admin_api/views/report_view.ex index b761dbb22..b4b0be267 100644 --- a/lib/pleroma/web/admin_api/views/report_view.ex +++ b/lib/pleroma/web/admin_api/views/report_view.ex @@ -6,9 +6,11 @@ defmodule Pleroma.Web.AdminAPI.ReportView do    use Pleroma.Web, :view    alias Pleroma.HTML +  alias Pleroma.Rule    alias Pleroma.User    alias Pleroma.Web.AdminAPI    alias Pleroma.Web.AdminAPI.Report +  alias Pleroma.Web.AdminAPI.RuleView    alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.MastodonAPI.StatusView @@ -46,7 +48,8 @@ defmodule Pleroma.Web.AdminAPI.ReportView do            as: :activity          }),        state: report.data["state"], -      notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}) +      notes: render(__MODULE__, "index_notes.json", %{notes: report.report_notes}), +      rules: rules(Map.get(report.data, "rules", nil))      }    end @@ -71,4 +74,16 @@ defmodule Pleroma.Web.AdminAPI.ReportView do        created_at: Utils.to_masto_date(inserted_at)      }    end + +  defp rules(nil) do +    [] +  end + +  defp rules(rule_ids) do +    rules = +      rule_ids +      |> Rule.get() + +    render(RuleView, "index.json", rules: rules) +  end  end diff --git a/lib/pleroma/web/admin_api/views/rule_view.ex b/lib/pleroma/web/admin_api/views/rule_view.ex new file mode 100644 index 000000000..606443f05 --- /dev/null +++ b/lib/pleroma/web/admin_api/views/rule_view.ex @@ -0,0 +1,22 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleView do +  use Pleroma.Web, :view + +  require Pleroma.Constants + +  def render("index.json", %{rules: rules} = _opts) do +    render_many(rules, __MODULE__, "show.json") +  end + +  def render("show.json", %{rule: rule} = _opts) do +    %{ +      id: to_string(rule.id), +      priority: rule.priority, +      text: rule.text, +      hint: rule.hint +    } +  end +end diff --git a/lib/pleroma/web/api_spec.ex b/lib/pleroma/web/api_spec.ex index 10d221571..314782818 100644 --- a/lib/pleroma/web/api_spec.ex +++ b/lib/pleroma/web/api_spec.ex @@ -97,6 +97,7 @@ defmodule Pleroma.Web.ApiSpec do                "Frontend management",                "Instance configuration",                "Instance documents", +              "Instance rule managment",                "Invites",                "MediaProxy cache",                "OAuth application management", diff --git a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex index fbb6896a9..25a604beb 100644 --- a/lib/pleroma/web/api_spec/operations/admin/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/admin/report_operation.ex @@ -31,6 +31,12 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do            "Filter by report state"          ),          Operation.parameter( +          :rule_id, +          :query, +          %Schema{type: :string}, +          "Filter by selected rule id" +        ), +        Operation.parameter(            :limit,            :query,            %Schema{type: :integer}, @@ -169,6 +175,17 @@ defmodule Pleroma.Web.ApiSpec.Admin.ReportOperation do                inserted_at: %Schema{type: :string, format: :"date-time"}              }            } +        }, +        rules: %Schema{ +          type: :array, +          items: %Schema{ +            type: :object, +            properties: %{ +              id: %Schema{type: :string}, +              text: %Schema{type: :string}, +              hint: %Schema{type: :string, nullable: true} +            } +          }          }        }      } diff --git a/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex new file mode 100644 index 000000000..c3a3ecc7c --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/rule_operation.ex @@ -0,0 +1,115 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.RuleOperation do +  alias OpenApiSpex.Operation +  alias OpenApiSpex.Schema +  alias Pleroma.Web.ApiSpec.Schemas.ApiError + +  import Pleroma.Web.ApiSpec.Helpers + +  def open_api_operation(action) do +    operation = String.to_existing_atom("#{action}_operation") +    apply(__MODULE__, operation, []) +  end + +  def index_operation do +    %Operation{ +      tags: ["Instance rule managment"], +      summary: "Retrieve list of instance rules", +      operationId: "AdminAPI.RuleController.index", +      security: [%{"oAuth" => ["admin:read"]}], +      responses: %{ +        200 => +          Operation.response("Response", "application/json", %Schema{ +            type: :array, +            items: rule() +          }), +        403 => Operation.response("Forbidden", "application/json", ApiError) +      } +    } +  end + +  def create_operation do +    %Operation{ +      tags: ["Instance rule managment"], +      summary: "Create new rule", +      operationId: "AdminAPI.RuleController.create", +      security: [%{"oAuth" => ["admin:write"]}], +      parameters: admin_api_params(), +      requestBody: request_body("Parameters", create_request(), required: true), +      responses: %{ +        200 => Operation.response("Response", "application/json", rule()), +        400 => Operation.response("Bad Request", "application/json", ApiError), +        403 => Operation.response("Forbidden", "application/json", ApiError) +      } +    } +  end + +  def update_operation do +    %Operation{ +      tags: ["Instance rule managment"], +      summary: "Modify existing rule", +      operationId: "AdminAPI.RuleController.update", +      security: [%{"oAuth" => ["admin:write"]}], +      parameters: [Operation.parameter(:id, :path, :string, "Rule ID")], +      requestBody: request_body("Parameters", update_request(), required: true), +      responses: %{ +        200 => Operation.response("Response", "application/json", rule()), +        400 => Operation.response("Bad Request", "application/json", ApiError), +        403 => Operation.response("Forbidden", "application/json", ApiError) +      } +    } +  end + +  def delete_operation do +    %Operation{ +      tags: ["Instance rule managment"], +      summary: "Delete rule", +      operationId: "AdminAPI.RuleController.delete", +      parameters: [Operation.parameter(:id, :path, :string, "Rule ID")], +      security: [%{"oAuth" => ["admin:write"]}], +      responses: %{ +        200 => empty_object_response(), +        404 => Operation.response("Not Found", "application/json", ApiError), +        403 => Operation.response("Forbidden", "application/json", ApiError) +      } +    } +  end + +  defp create_request do +    %Schema{ +      type: :object, +      required: [:text], +      properties: %{ +        priority: %Schema{type: :integer}, +        text: %Schema{type: :string}, +        hint: %Schema{type: :string} +      } +    } +  end + +  defp update_request do +    %Schema{ +      type: :object, +      properties: %{ +        priority: %Schema{type: :integer}, +        text: %Schema{type: :string}, +        hint: %Schema{type: :string} +      } +    } +  end + +  defp rule do +    %Schema{ +      type: :object, +      properties: %{ +        id: %Schema{type: :string}, +        priority: %Schema{type: :integer}, +        text: %Schema{type: :string}, +        hint: %Schema{type: :string, nullable: true} +      } +    } +  end +end diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index b6c411c07..7d7a5ecc1 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -46,6 +46,17 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do      }    end +  def rules_operation do +    %Operation{ +      tags: ["Instance misc"], +      summary: "Retrieve list of instance rules", +      operationId: "InstanceController.rules", +      responses: %{ +        200 => Operation.response("Array of domains", "application/json", array_of_rules()) +      } +    } +  end +    defp instance do      %Schema{        type: :object, @@ -181,7 +192,8 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do          "urls" => %{            "streaming_api" => "wss://lain.com"          }, -        "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)" +        "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)", +        "rules" => array_of_rules()        }      }    end @@ -371,4 +383,18 @@ defmodule Pleroma.Web.ApiSpec.InstanceOperation do        example: ["pleroma.site", "lain.com", "bikeshed.party"]      }    end + +  defp array_of_rules do +    %Schema{ +      type: :array, +      items: %Schema{ +        type: :object, +        properties: %{ +          id: %Schema{type: :string}, +          text: %Schema{type: :string}, +          hint: %Schema{type: :string} +        } +      } +    } +  end  end diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex index c74ac7d5f..f5f88974c 100644 --- a/lib/pleroma/web/api_spec/operations/report_operation.ex +++ b/lib/pleroma/web/api_spec/operations/report_operation.ex @@ -53,6 +53,12 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do            default: false,            description:              "If the account is remote, should the report be forwarded to the remote admin?" +        }, +        rule_ids: %Schema{ +          type: :array, +          nullable: true, +          items: %Schema{type: :string}, +          description: "Array of rules"          }        },        required: [:account_id], @@ -60,7 +66,8 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do          "account_id" => "123",          "status_ids" => ["1337"],          "comment" => "bad status!", -        "forward" => "false" +        "forward" => "false", +        "rule_ids" => ["3"]        }      }    end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 27e82ecc8..34e480d73 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.CommonAPI do    alias Pleroma.Formatter    alias Pleroma.ModerationLog    alias Pleroma.Object +  alias Pleroma.Rule    alias Pleroma.ThreadMute    alias Pleroma.User    alias Pleroma.UserRelationship @@ -568,14 +569,16 @@ defmodule Pleroma.Web.CommonAPI do    def report(user, data) do      with {:ok, account} <- get_reported_account(data.account_id),           {:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]), -         {:ok, statuses} <- get_report_statuses(account, data) do +         {:ok, statuses} <- get_report_statuses(account, data), +         rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do        ActivityPub.flag(%{          context: Utils.generate_context_id(),          actor: user,          account: account,          statuses: statuses,          content: content_html, -        forward: Map.get(data, :forward, false) +        forward: Map.get(data, :forward, false), +        rules: rules        })      end    end @@ -587,6 +590,15 @@ defmodule Pleroma.Web.CommonAPI do      end    end +  defp get_report_rules(nil) do +    nil +  end + +  defp get_report_rules(rule_ids) do +    rule_ids +    |> Enum.filter(&Rule.exists?/1) +  end +    def update_report_state(activity_ids, state) when is_list(activity_ids) do      case Utils.update_report_state(activity_ids, state) do        :ok -> {:ok, activity_ids} diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex index 3e664903a..b97b0e476 100644 --- a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex @@ -25,4 +25,9 @@ defmodule Pleroma.Web.MastodonAPI.InstanceController do    def peers(conn, _params) do      json(conn, Pleroma.Stats.get_peers())    end + +  @doc "GET /api/v1/instance/rules" +  def rules(conn, _params) do +    render(conn, "rules.json") +  end  end diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 890dd3977..99fc6d0c3 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -76,12 +76,26 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do      })    end +  def render("rules.json", _) do +    Pleroma.Rule.query() +    |> Pleroma.Repo.all() +    |> render_many(__MODULE__, "rule.json", as: :rule) +  end + +  def render("rule.json", %{rule: rule}) do +    %{ +      id: to_string(rule.id), +      text: rule.text, +      hint: rule.hint || "" +    } +  end +    defp common_information(instance) do      %{ -      title: Keyword.get(instance, :name), -      version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",        languages: Keyword.get(instance, :languages, ["en"]), -      rules: [] +      rules: render(__MODULE__, "rules.json"), +      title: Keyword.get(instance, :name), +      version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})"      }    end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 86d6da883..e35a89ce2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -292,6 +292,11 @@ defmodule Pleroma.Web.Router do      post("/frontends/install", FrontendController, :install)      post("/backups", AdminAPIController, :create_backup) + +    get("/rules", RuleController, :index) +    post("/rules", RuleController, :create) +    patch("/rules/:id", RuleController, :update) +    delete("/rules/:id", RuleController, :delete)    end    # AdminAPI: admins and mods (staff) can perform these actions (if privileged by role) @@ -764,6 +769,7 @@ defmodule Pleroma.Web.Router do      get("/instance", InstanceController, :show)      get("/instance/peers", InstanceController, :peers) +    get("/instance/rules", InstanceController, :rules)      get("/statuses", StatusController, :index)      get("/statuses/:id", StatusController, :show) diff --git a/priv/repo/migrations/20220203224011_create_rules.exs b/priv/repo/migrations/20220203224011_create_rules.exs new file mode 100644 index 000000000..16f29ca53 --- /dev/null +++ b/priv/repo/migrations/20220203224011_create_rules.exs @@ -0,0 +1,12 @@ +defmodule Pleroma.Repo.Migrations.CreateRules do +  use Ecto.Migration + +  def change do +    create_if_not_exists table(:rules) do +      add(:priority, :integer, default: 0, null: false) +      add(:text, :text, null: false) + +      timestamps() +    end +  end +end diff --git a/priv/repo/migrations/20240406000000_add_hint_to_rules.exs b/priv/repo/migrations/20240406000000_add_hint_to_rules.exs new file mode 100644 index 000000000..273290560 --- /dev/null +++ b/priv/repo/migrations/20240406000000_add_hint_to_rules.exs @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.AddHintToRules do +  use Ecto.Migration + +  def change do +    alter table(:rules) do +      add_if_not_exists(:hint, :text) +    end +  end +end diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 4cf14e65b..392fd53c2 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.NotificationTest do    use Pleroma.DataCase, async: false    import Pleroma.Factory -  import Mock    alias Pleroma.FollowingRelationship    alias Pleroma.Notification @@ -18,8 +17,6 @@ defmodule Pleroma.NotificationTest do    alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.NotificationView -  alias Pleroma.Web.Push -  alias Pleroma.Web.Streamer    setup do      Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Config) @@ -175,158 +172,7 @@ defmodule Pleroma.NotificationTest do      assert [user2.id, user3.id, user1.id] == Enum.map(notifications, & &1.user_id)    end -  describe "CommonApi.post/2 notification-related functionality" do -    test_with_mock "creates but does NOT send notification to blocker user", -                   Push, -                   [:passthrough], -                   [] do -      user = insert(:user) -      blocker = insert(:user) -      {:ok, _user_relationship} = User.block(blocker, user) - -      {:ok, _activity} = CommonAPI.post(user, %{status: "hey @#{blocker.nickname}!"}) - -      blocker_id = blocker.id -      assert [%Notification{user_id: ^blocker_id}] = Repo.all(Notification) -      refute called(Push.send(:_)) -    end - -    test_with_mock "creates but does NOT send notification to notification-muter user", -                   Push, -                   [:passthrough], -                   [] do -      user = insert(:user) -      muter = insert(:user) -      {:ok, _user_relationships} = User.mute(muter, user) - -      {:ok, _activity} = CommonAPI.post(user, %{status: "hey @#{muter.nickname}!"}) - -      muter_id = muter.id -      assert [%Notification{user_id: ^muter_id}] = Repo.all(Notification) -      refute called(Push.send(:_)) -    end - -    test_with_mock "creates but does NOT send notification to thread-muter user", -                   Push, -                   [:passthrough], -                   [] do -      user = insert(:user) -      thread_muter = insert(:user) - -      {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{thread_muter.nickname}!"}) - -      {:ok, _} = CommonAPI.add_mute(thread_muter, activity) - -      {:ok, _same_context_activity} = -        CommonAPI.post(user, %{ -          status: "hey-hey-hey @#{thread_muter.nickname}!", -          in_reply_to_status_id: activity.id -        }) - -      [pre_mute_notification, post_mute_notification] = -        Repo.all(from(n in Notification, where: n.user_id == ^thread_muter.id, order_by: n.id)) - -      pre_mute_notification_id = pre_mute_notification.id -      post_mute_notification_id = post_mute_notification.id - -      assert called( -               Push.send( -                 :meck.is(fn -                   %Notification{id: ^pre_mute_notification_id} -> true -                   _ -> false -                 end) -               ) -             ) - -      refute called( -               Push.send( -                 :meck.is(fn -                   %Notification{id: ^post_mute_notification_id} -> true -                   _ -> false -                 end) -               ) -             ) -    end -  end -    describe "create_notification" do -    @tag needs_streamer: true -    test "it creates a notification for user and send to the 'user' and the 'user:notification' stream" do -      %{user: user, token: oauth_token} = oauth_access(["read"]) - -      task = -        Task.async(fn -> -          {:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token) -          assert_receive {:render_with_user, _, _, _, _}, 4_000 -        end) - -      task_user_notification = -        Task.async(fn -> -          {:ok, _topic} = -            Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) - -          assert_receive {:render_with_user, _, _, _, _}, 4_000 -        end) - -      activity = insert(:note_activity) - -      notify = Notification.create_notification(activity, user) -      assert notify.user_id == user.id -      Task.await(task) -      Task.await(task_user_notification) -    end - -    test "it creates a notification for user if the user blocks the activity author" do -      activity = insert(:note_activity) -      author = User.get_cached_by_ap_id(activity.data["actor"]) -      user = insert(:user) -      {:ok, _user_relationship} = User.block(user, author) - -      assert Notification.create_notification(activity, user) -    end - -    test "it creates a notification for the user if the user mutes the activity author" do -      muter = insert(:user) -      muted = insert(:user) -      {:ok, _} = User.mute(muter, muted) -      muter = Repo.get(User, muter.id) -      {:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"}) - -      notification = Notification.create_notification(activity, muter) - -      assert notification.id -      assert notification.seen -    end - -    test "notification created if user is muted without notifications" do -      muter = insert(:user) -      muted = insert(:user) - -      {:ok, _user_relationships} = User.mute(muter, muted, %{notifications: false}) - -      {:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"}) - -      assert Notification.create_notification(activity, muter) -    end - -    test "it creates a notification for an activity from a muted thread" do -      muter = insert(:user) -      other_user = insert(:user) -      {:ok, activity} = CommonAPI.post(muter, %{status: "hey"}) -      CommonAPI.add_mute(muter, activity) - -      {:ok, activity} = -        CommonAPI.post(other_user, %{ -          status: "Hi @#{muter.nickname}", -          in_reply_to_status_id: activity.id -        }) - -      notification = Notification.create_notification(activity, muter) - -      assert notification.id -      assert notification.seen -    end -      test "it disables notifications from strangers" do        follower = insert(:user) @@ -680,7 +526,7 @@ defmodule Pleroma.NotificationTest do            status: "hey @#{other_user.nickname}!"          }) -      {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert other_user in enabled_receivers      end @@ -712,7 +558,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = Transmogrifier.handle_incoming(create_activity) -      {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert other_user in enabled_receivers      end @@ -739,7 +585,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = Transmogrifier.handle_incoming(create_activity) -      {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert other_user not in enabled_receivers      end @@ -756,8 +602,7 @@ defmodule Pleroma.NotificationTest do        {:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id) -      {enabled_receivers, _disabled_receivers} = -        Notification.get_notified_from_activity(activity_two) +      enabled_receivers = Notification.get_notified_from_activity(activity_two)        assert other_user not in enabled_receivers      end @@ -779,7 +624,7 @@ defmodule Pleroma.NotificationTest do          |> Map.put("to", [other_user.ap_id | like_data["to"]])          |> ActivityPub.persist(local: true) -      {enabled_receivers, _disabled_receivers} = Notification.get_notified_from_activity(like) +      enabled_receivers = Notification.get_notified_from_activity(like)        assert other_user not in enabled_receivers      end @@ -796,39 +641,36 @@ defmodule Pleroma.NotificationTest do        {:ok, activity_two} = CommonAPI.repeat(activity_one.id, third_user) -      {enabled_receivers, _disabled_receivers} = -        Notification.get_notified_from_activity(activity_two) +      enabled_receivers = Notification.get_notified_from_activity(activity_two)        assert other_user not in enabled_receivers      end -    test "it returns blocking recipient in disabled recipients list" do +    test "it does not return blocking recipient in recipients list" do        user = insert(:user)        other_user = insert(:user)        {:ok, _user_relationship} = User.block(other_user, user)        {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"}) -      {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert [] == enabled_receivers -      assert [other_user] == disabled_receivers      end -    test "it returns notification-muting recipient in disabled recipients list" do +    test "it does not return notification-muting recipient in recipients list" do        user = insert(:user)        other_user = insert(:user)        {:ok, _user_relationships} = User.mute(other_user, user)        {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"}) -      {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert [] == enabled_receivers -      assert [other_user] == disabled_receivers      end -    test "it returns thread-muting recipient in disabled recipients list" do +    test "it does not return thread-muting recipient in recipients list" do        user = insert(:user)        other_user = insert(:user) @@ -842,14 +684,12 @@ defmodule Pleroma.NotificationTest do            in_reply_to_status_id: activity.id          }) -      {enabled_receivers, disabled_receivers} = -        Notification.get_notified_from_activity(same_context_activity) +      enabled_receivers = Notification.get_notified_from_activity(same_context_activity) -      assert [other_user] == disabled_receivers        refute other_user in enabled_receivers      end -    test "it returns non-following domain-blocking recipient in disabled recipients list" do +    test "it does not return non-following domain-blocking recipient in recipients list" do        blocked_domain = "blocked.domain"        user = insert(:user, %{ap_id: "https://#{blocked_domain}/@actor"})        other_user = insert(:user) @@ -858,10 +698,9 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"}) -      {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert [] == enabled_receivers -      assert [other_user] == disabled_receivers      end      test "it returns following domain-blocking recipient in enabled recipients list" do @@ -874,10 +713,9 @@ defmodule Pleroma.NotificationTest do        {:ok, activity} = CommonAPI.post(user, %{status: "hey @#{other_user.nickname}!"}) -      {enabled_receivers, disabled_receivers} = Notification.get_notified_from_activity(activity) +      enabled_receivers = Notification.get_notified_from_activity(activity)        assert [other_user] == enabled_receivers -      assert [] == disabled_receivers      end      test "it sends edited notifications to those who repeated a status" do @@ -897,11 +735,10 @@ defmodule Pleroma.NotificationTest do            status: "hey @#{other_user.nickname}! mew mew"          }) -      {enabled_receivers, _disabled_receivers} = -        Notification.get_notified_from_activity(edit_activity) +      enabled_receivers = Notification.get_notified_from_activity(edit_activity)        assert repeated_user in enabled_receivers -      assert other_user not in enabled_receivers +      refute other_user in enabled_receivers      end    end @@ -1189,13 +1026,13 @@ defmodule Pleroma.NotificationTest do        assert Notification.for_user(user) == []      end -    test "it returns notifications from a muted user when with_muted is set", %{user: user} do +    test "it doesn't return notifications from a muted user when with_muted is set", %{user: user} do        muted = insert(:user)        {:ok, _user_relationships} = User.mute(user, muted)        {:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"}) -      assert length(Notification.for_user(user, %{with_muted: true})) == 1 +      assert Enum.empty?(Notification.for_user(user, %{with_muted: true}))      end      test "it doesn't return notifications from a blocked user when with_muted is set", %{ diff --git a/test/pleroma/rule_test.exs b/test/pleroma/rule_test.exs new file mode 100644 index 000000000..d710a6312 --- /dev/null +++ b/test/pleroma/rule_test.exs @@ -0,0 +1,57 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.RuleTest do +  use Pleroma.DataCase, async: true + +  alias Pleroma.Repo +  alias Pleroma.Rule + +  test "getting a list of rules sorted by priority" do +    %{id: id1} = Rule.create(%{text: "Example rule"}) +    %{id: id2} = Rule.create(%{text: "Second rule", priority: 2}) +    %{id: id3} = Rule.create(%{text: "Third rule", priority: 1}) + +    rules = +      Rule.query() +      |> Repo.all() + +    assert [%{id: ^id1}, %{id: ^id3}, %{id: ^id2}] = rules +  end + +  test "creating rules" do +    %{id: id} = Rule.create(%{text: "Example rule"}) + +    assert %{text: "Example rule"} = Rule.get(id) +  end + +  test "editing rules" do +    %{id: id} = Rule.create(%{text: "Example rule"}) + +    Rule.update(%{text: "There are no rules", priority: 2}, id) + +    assert %{text: "There are no rules", priority: 2} = Rule.get(id) +  end + +  test "deleting rules" do +    %{id: id} = Rule.create(%{text: "Example rule"}) + +    Rule.delete(id) + +    assert [] = +             Rule.query() +             |> Pleroma.Repo.all() +  end + +  test "getting rules by ids" do +    %{id: id1} = Rule.create(%{text: "Example rule"}) +    %{id: id2} = Rule.create(%{text: "Second rule"}) +    %{id: _id3} = Rule.create(%{text: "Third rule"}) + +    rules = Rule.get([id1, id2]) + +    assert Enum.all?(rules, &(&1.id in [id1, id2])) +    assert length(rules) == 2 +  end +end diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 94cc80b76..7af50e12c 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -827,31 +827,6 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do        {:ok, announce, _} = SideEffects.handle(announce)        assert Repo.get_by(Notification, user_id: poster.id, activity_id: announce.id)      end - -    test "it streams out the announce", %{announce: announce} do -      with_mocks([ -        { -          Pleroma.Web.Streamer, -          [], -          [ -            stream: fn _, _ -> nil end -          ] -        }, -        { -          Pleroma.Web.Push, -          [], -          [ -            send: fn _ -> nil end -          ] -        } -      ]) do -        {:ok, announce, _} = SideEffects.handle(announce) - -        assert called(Pleroma.Web.Streamer.stream(["user", "list"], announce)) - -        assert called(Pleroma.Web.Push.send(:_)) -      end -    end    end    describe "removing a follower" do 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 fb2579a3d..b626ddf55 100644 --- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs @@ -11,6 +11,7 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do    alias Pleroma.ModerationLog    alias Pleroma.Repo    alias Pleroma.ReportNote +  alias Pleroma.Rule    alias Pleroma.Web.CommonAPI    setup do @@ -436,6 +437,34 @@ defmodule Pleroma.Web.AdminAPI.ReportControllerTest do                 "error" => "Invalid credentials."               }      end + +    test "returns reports with specified role_id", %{conn: conn} do +      [reporter, target_user] = insert_pair(:user) + +      %{id: rule_id} = Rule.create(%{text: "Example rule"}) + +      rule_id = to_string(rule_id) + +      {:ok, %{id: report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "", +          rule_ids: [rule_id] +        }) + +      {:ok, _report} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "" +        }) + +      response = +        conn +        |> get("/api/pleroma/admin/reports?rule_id=#{rule_id}") +        |> json_response_and_validate_schema(:ok) + +      assert %{"reports" => [%{"id" => ^report_id}]} = response +    end    end    describe "POST /api/pleroma/admin/reports/:id/notes" do diff --git a/test/pleroma/web/admin_api/controllers/rule_controller_test.exs b/test/pleroma/web/admin_api/controllers/rule_controller_test.exs new file mode 100644 index 000000000..96b52b272 --- /dev/null +++ b/test/pleroma/web/admin_api/controllers/rule_controller_test.exs @@ -0,0 +1,82 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RuleControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  import Pleroma.Factory + +  alias Pleroma.Rule + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "GET /api/pleroma/admin/rules" do +    test "sorts rules by priority", %{conn: conn} do +      %{id: id1} = Rule.create(%{text: "Example rule"}) +      %{id: id2} = Rule.create(%{text: "Second rule", priority: 2}) +      %{id: id3} = Rule.create(%{text: "Third rule", priority: 1}) + +      id1 = to_string(id1) +      id2 = to_string(id2) +      id3 = to_string(id3) + +      response = +        conn +        |> get("/api/pleroma/admin/rules") +        |> json_response_and_validate_schema(:ok) + +      assert [%{"id" => ^id1}, %{"id" => ^id3}, %{"id" => ^id2}] = response +    end +  end + +  describe "POST /api/pleroma/admin/rules" do +    test "creates a rule", %{conn: conn} do +      %{"id" => id} = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/rules", %{text: "Example rule"}) +        |> json_response_and_validate_schema(:ok) + +      assert %{text: "Example rule"} = Rule.get(id) +    end +  end + +  describe "PATCH /api/pleroma/admin/rules" do +    test "edits a rule", %{conn: conn} do +      %{id: id} = Rule.create(%{text: "Example rule"}) + +      conn +      |> put_req_header("content-type", "application/json") +      |> patch("/api/pleroma/admin/rules/#{id}", %{text: "There are no rules", priority: 2}) +      |> json_response_and_validate_schema(:ok) + +      assert %{text: "There are no rules", priority: 2} = Rule.get(id) +    end +  end + +  describe "DELETE /api/pleroma/admin/rules" do +    test "deletes a rule", %{conn: conn} do +      %{id: id} = Rule.create(%{text: "Example rule"}) + +      conn +      |> put_req_header("content-type", "application/json") +      |> delete("/api/pleroma/admin/rules/#{id}") +      |> json_response_and_validate_schema(:ok) + +      assert [] = +               Rule.query() +               |> Pleroma.Repo.all() +    end +  end +end diff --git a/test/pleroma/web/admin_api/views/report_view_test.exs b/test/pleroma/web/admin_api/views/report_view_test.exs index 9637c2b90..1b16aca6a 100644 --- a/test/pleroma/web/admin_api/views/report_view_test.exs +++ b/test/pleroma/web/admin_api/views/report_view_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do    import Pleroma.Factory +  alias Pleroma.Rule    alias Pleroma.Web.AdminAPI    alias Pleroma.Web.AdminAPI.Report    alias Pleroma.Web.AdminAPI.ReportView @@ -38,7 +39,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do        statuses: [],        notes: [],        state: "open", -      id: activity.id +      id: activity.id, +      rules: []      }      result = @@ -76,7 +78,8 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do        statuses: [StatusView.render("show.json", %{activity: activity})],        state: "open",        notes: [], -      id: report_activity.id +      id: report_activity.id, +      rules: []      }      result = @@ -168,4 +171,22 @@ defmodule Pleroma.Web.AdminAPI.ReportViewTest do      assert report2.id == rendered |> Enum.at(0) |> Map.get(:id)      assert report1.id == rendered |> Enum.at(1) |> Map.get(:id)    end + +  test "renders included rules" do +    user = insert(:user) +    other_user = insert(:user) + +    %{id: rule_id, text: text} = Rule.create(%{text: "Example rule"}) + +    rule_id = to_string(rule_id) + +    {:ok, activity} = +      CommonAPI.report(user, %{ +        account_id: other_user.id, +        rule_ids: [rule_id] +      }) + +    assert %{rules: [%{id: ^rule_id, text: ^text}]} = +             ReportView.render("show.json", Report.extract_report_info(activity)) +  end  end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 20984eb08..58cd1fd42 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -12,6 +12,7 @@ defmodule Pleroma.Web.CommonAPITest do    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.Rule    alias Pleroma.UnstubbedConfigMock, as: ConfigMock    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub @@ -1363,6 +1364,33 @@ defmodule Pleroma.Web.CommonAPITest do        assert first_report.data["state"] == "resolved"        assert second_report.data["state"] == "resolved"      end + +    test "creates a report with provided rules" do +      reporter = insert(:user) +      target_user = insert(:user) + +      %{id: rule_id} = Rule.create(%{text: "There are no rules"}) + +      reporter_ap_id = reporter.ap_id +      target_ap_id = target_user.ap_id + +      report_data = %{ +        account_id: target_user.id, +        rule_ids: [rule_id] +      } + +      assert {:ok, flag_activity} = CommonAPI.report(reporter, report_data) + +      assert %Activity{ +               actor: ^reporter_ap_id, +               data: %{ +                 "type" => "Flag", +                 "object" => [^target_ap_id], +                 "state" => "open", +                 "rules" => [^rule_id] +               } +             } = flag_activity +    end    end    describe "reblog muting" do diff --git a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs index 353ed1a72..373a84303 100644 --- a/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/instance_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do    # TODO: Should not need Cachex    use Pleroma.Web.ConnCase +  alias Pleroma.Rule    alias Pleroma.User    import Pleroma.Factory @@ -40,7 +41,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do               "banner_upload_limit" => _,               "background_image" => from_config_background,               "shout_limit" => _, -             "description_limit" => _ +             "description_limit" => _, +             "rules" => _             } = result      assert result["pleroma"]["metadata"]["account_activation_required"] != nil @@ -125,4 +127,29 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do      assert get(conn, "/api/v2/instance")             |> json_response_and_validate_schema(200)    end + +  test "get instance rules", %{conn: conn} do +    Rule.create(%{text: "Example rule", hint: "Rule description", priority: 1}) +    Rule.create(%{text: "Third rule", priority: 2}) +    Rule.create(%{text: "Second rule", priority: 1}) + +    conn = get(conn, "/api/v1/instance") + +    assert result = json_response_and_validate_schema(conn, 200) + +    assert [ +             %{ +               "text" => "Example rule", +               "hint" => "Rule description" +             }, +             %{ +               "text" => "Second rule", +               "hint" => "" +             }, +             %{ +               "text" => "Third rule", +               "hint" => "" +             } +           ] = result["rules"] +  end  end diff --git a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs index c7aa76122..4ab5d0771 100644 --- a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs @@ -7,6 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do    alias Pleroma.Activity    alias Pleroma.Repo +  alias Pleroma.Rule    alias Pleroma.Web.CommonAPI    import Pleroma.Factory @@ -81,6 +82,44 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do               |> json_response_and_validate_schema(200)    end +  test "submit a report with rule_ids", %{ +    conn: conn, +    target_user: target_user +  } do +    %{id: rule_id} = Rule.create(%{text: "There are no rules"}) + +    rule_id = to_string(rule_id) + +    assert %{"action_taken" => false, "id" => id} = +             conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/reports", %{ +               "account_id" => target_user.id, +               "forward" => "false", +               "rule_ids" => [rule_id] +             }) +             |> json_response_and_validate_schema(200) + +    assert %Activity{data: %{"rules" => [^rule_id]}} = Activity.get_report(id) +  end + +  test "rules field is empty if provided wrong rule id", %{ +    conn: conn, +    target_user: target_user +  } do +    assert %{"id" => id} = +             conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/reports", %{ +               "account_id" => target_user.id, +               "forward" => "false", +               "rule_ids" => ["-1"] +             }) +             |> json_response_and_validate_schema(200) + +    assert %Activity{data: %{"rules" => []}} = Activity.get_report(id) +  end +    test "account_id is required", %{      conn: conn,      activity: activity  | 
