Merge branch 'feature/521-pinned-post-federation' into 'develop'
authorrinpatch <rin@patch.cx>
Fri, 16 Apr 2021 09:53:47 +0000 (09:53 +0000)
committerrinpatch <rin@patch.cx>
Fri, 16 Apr 2021 09:53:47 +0000 (09:53 +0000)
Pinned posts federation

Closes #521

See merge request pleroma/pleroma!3312

16 files changed:
CHANGELOG.md
config/config.exs
config/description.exs
docs/configuration/cheatsheet.md
docs/index.md
lib/pleroma/config/release_runtime_provider.ex
lib/pleroma/config_db.ex
lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex [new file with mode: 0644]
lib/pleroma/web/activity_pub/object_validator.ex
lib/pleroma/web/mastodon_api/views/instance_view.ex
mix.exs
priv/repo/migrations/20210401143153_user_notification_settings_fix.exs [new file with mode: 0644]
test/fixtures/config/temp.exported_from_db.secret.exs [new file with mode: 0644]
test/pleroma/config/release_runtime_provider_test.exs [new file with mode: 0644]
test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs [new file with mode: 0644]
test/pleroma/web/admin_api/controllers/config_controller_test.exs

index fb26c7a736a9929a5b82dafcc35e16553e40781f..9b0678023f9e83f4168555f94f38b5e87bce598c 100644 (file)
@@ -6,13 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## Unreleased
 
+### Changed
+
 - The `application` metadata returned with statuses is no longer hardcoded. Apps that want to display these details will now have valid data for new posts after this change.
 
+### Added
+
+- MRF (`FollowBotPolicy`): New MRF Policy which makes a designated local Bot account attempt to follow all users in public Notes received by your instance. Users who require approving follower requests or have #nobot in their profile are excluded.
+
 ## Unreleased (Patch)
 
 ### Fixed
 
 - Try to save exported ConfigDB settings (migrate_from_db) in the system temp directory if default location is not writable.
+- Uploading custom instance thumbnail via AdminAPI/AdminFE generated invalid URL to the image
+- Applying ConcurrentLimiter settings via AdminAPI
+- User login failures if their `notification_settings` were in a NULL state.
 
 ## [2.3.0] - 2020-03-01
 
index 8d1e17b42bd44d58accaf52e24b9f742e67fcbfd..4381068ac34afa3fa488672f5716a777bbc7b427 100644 (file)
@@ -409,6 +409,8 @@ config :pleroma, :mrf_object_age,
   threshold: 604_800,
   actions: [:delist, :strip_followers]
 
+config :pleroma, :mrf_follow_bot, follower_nickname: nil
+
 config :pleroma, :rich_media,
   enabled: true,
   ignore_hosts: [],
index 41e5e4056e6b3e79e7dee9ce470f546239c5e209..bb1f4330565b0ea8e64480dab4585ff44113d502 100644 (file)
@@ -2942,6 +2942,23 @@ config :pleroma, :config_description, [
       }
     ]
   },
+  %{
+    group: :pleroma,
+    key: :mrf_follow_bot,
+    tab: :mrf,
+    related_policy: "Pleroma.Web.ActivityPub.MRF.FollowBotPolicy",
+    label: "MRF FollowBot Policy",
+    type: :group,
+    description: "Automatically follows newly discovered accounts.",
+    children: [
+      %{
+        key: :follower_nickname,
+        type: :string,
+        description: "The name of the bot account to use for following newly discovered users.",
+        suggestions: ["followbot"]
+      }
+    ]
+  },
   %{
     group: :pleroma,
     key: :modules,
index 8f2c4347ee869fa37bca1d699ab310919867493d..0694217225f040bd0c0809ce376fc1b58661e250 100644 (file)
@@ -124,6 +124,7 @@ To add configuration to your config file, you can copy it from the base config.
     * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
     * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections.
     * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines.
+    * `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed.
 * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
 * `transparency_exclusions`: Exclude specific instance names from MRF transparency.  The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
 
@@ -220,6 +221,11 @@ Notes:
 - The hashtags in the configuration do not have a leading `#`.
 - This MRF Policy is always enabled, if you want to disable it you have to set empty lists
 
+#### :mrf_follow_bot
+
+* `follower_nickname`: The name of the bot account to use for following newly discovered users. Using `followbot` or similar is strongly suggested.
+
+
 ### :activitypub
 * `unfollow_blocked`: Whether blocks result in people getting unfollowed
 * `outgoing_blocks`: Whether to federate blocks to other instances
index 1a90d0a8dfb631de66b0f1f0f7c088e9b2060605..80c5d2631f1bf2fd136d52d90fd37586a7eb0ed6 100644 (file)
@@ -20,7 +20,7 @@ The default front-end used by Pleroma is Pleroma-FE. You can find more informati
 
 ### Mastodon interface
 If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too!
-Just add a "/web" after your instance url (e.g. <https://pleroma.soycaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
+Just add a "/web" after your instance url (e.g. <https://pleroma.soykaf.com/web>) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC!
 The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation.
 
 Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma.
index 8227195dcc58b2dde42e8baf92ec756843423dc1..e5e9d3dcd2c2a1019d990e76bfdaa9abb154e589 100644 (file)
@@ -1,6 +1,6 @@
 defmodule Pleroma.Config.ReleaseRuntimeProvider do
   @moduledoc """
-  Imports `runtime.exs` and `{env}.exported_from_db.secret.exs` for elixir releases.
+  Imports runtime config and `{env}.exported_from_db.secret.exs` for releases.
   """
   @behaviour Config.Provider
 
@@ -8,10 +8,11 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
   def init(opts), do: opts
 
   @impl true
-  def load(config, _opts) do
+  def load(config, opts) do
     with_defaults = Config.Reader.merge(config, Pleroma.Config.Holder.release_defaults())
 
-    config_path = System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
+    config_path =
+      opts[:config_path] || System.get_env("PLEROMA_CONFIG_PATH") || "/etc/pleroma/config.exs"
 
     with_runtime_config =
       if File.exists?(config_path) do
@@ -24,7 +25,7 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
         warning = [
           IO.ANSI.red(),
           IO.ANSI.bright(),
-          "!!! #{config_path} not found! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
+          "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file",
           IO.ANSI.reset()
         ]
 
@@ -33,13 +34,14 @@ defmodule Pleroma.Config.ReleaseRuntimeProvider do
       end
 
     exported_config_path =
-      config_path
-      |> Path.dirname()
-      |> Path.join("prod.exported_from_db.secret.exs")
+      opts[:exported_config_path] ||
+        config_path
+        |> Path.dirname()
+        |> Path.join("#{Pleroma.Config.get(:env)}.exported_from_db.secret.exs")
 
     with_exported =
       if File.exists?(exported_config_path) do
-        exported_config = Config.Reader.read!(with_runtime_config)
+        exported_config = Config.Reader.read!(exported_config_path)
         Config.Reader.merge(with_runtime_config, exported_config)
       else
         with_runtime_config
index b874e0e37a08419ea7b16042fbe866d277a64a77..cb57673e39227aefc4ebac6ff979dbbf4b2bb2e1 100644 (file)
@@ -387,6 +387,6 @@ defmodule Pleroma.ConfigDB do
   @spec module_name?(String.t()) :: boolean()
   def module_name?(string) do
     Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or
-      string in ["Oban", "Ueberauth", "ExSyslogger"]
+      string in ["Oban", "Ueberauth", "ExSyslogger", "ConcurrentLimiter"]
   end
 end
diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex
new file mode 100644 (file)
index 0000000..7307c9c
--- /dev/null
@@ -0,0 +1,59 @@
+defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do
+  @behaviour Pleroma.Web.ActivityPub.MRF
+  alias Pleroma.Config
+  alias Pleroma.User
+  alias Pleroma.Web.CommonAPI
+
+  require Logger
+
+  @impl true
+  def filter(message) do
+    with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]),
+         %User{actor_type: "Service"} = follower <-
+           User.get_cached_by_nickname(follower_nickname),
+         %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do
+      try_follow(follower, message)
+    else
+      nil ->
+        Logger.warn(
+          "#{__MODULE__} skipped because of missing `:mrf_follow_bot, :follower_nickname` configuration, the :follower_nickname
+            account does not exist, or the account is not correctly configured as a bot."
+        )
+
+        {:ok, message}
+
+      _ ->
+        {:ok, message}
+    end
+  end
+
+  defp try_follow(follower, message) do
+    to = Map.get(message, "to", [])
+    cc = Map.get(message, "cc", [])
+    actor = [message["actor"]]
+
+    Enum.concat([to, cc, actor])
+    |> List.flatten()
+    |> Enum.uniq()
+    |> User.get_all_by_ap_id()
+    |> Enum.each(fn user ->
+      with false <- user.local,
+           false <- User.following?(follower, user),
+           false <- User.locked?(user),
+           false <- (user.bio || "") |> String.downcase() |> String.contains?("nobot") do
+        Logger.debug(
+          "#{__MODULE__}: Follow request from #{follower.nickname} to #{user.nickname}"
+        )
+
+        CommonAPI.follow(follower, user)
+      end
+    end)
+
+    {:ok, message}
+  end
+
+  @impl true
+  def describe do
+    {:ok, %{}}
+  end
+end
index 14c3e853180bb910af1c16fd3a3b2885b5ff9da8..25df36cae361d100be7bcc82c5079b03069b87e6 100644 (file)
@@ -38,37 +38,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
   @impl true
   def validate(object, meta)
 
-  def validate(%{"type" => type} = object, meta)
-      when type in ~w[Accept Reject] do
-    with {:ok, object} <-
-           object
-           |> AcceptRejectValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Event"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> EventValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Follow"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> FollowValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
   def validate(%{"type" => "Block"} = block_activity, meta) do
     with {:ok, block_activity} <-
            block_activity
@@ -88,16 +57,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
     end
   end
 
-  def validate(%{"type" => "Update"} = update_activity, meta) do
-    with {:ok, update_activity} <-
-           update_activity
-           |> UpdateValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      update_activity = stringify_keys(update_activity)
-      {:ok, update_activity, meta}
-    end
-  end
-
   def validate(%{"type" => "Undo"} = object, meta) do
     with {:ok, object} <-
            object
@@ -124,76 +83,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
     end
   end
 
-  def validate(%{"type" => "Like"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> LikeValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "ChatMessage"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> ChatMessageValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Question"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> QuestionValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => type} = object, meta) when type in ~w[Audio Video] do
-    with {:ok, object} <-
-           object
-           |> AudioVideoValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Article"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> ArticleNoteValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "Answer"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> AnswerValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
-  def validate(%{"type" => "EmojiReact"} = object, meta) do
-    with {:ok, object} <-
-           object
-           |> EmojiReactValidator.cast_and_validate()
-           |> Ecto.Changeset.apply_action(:insert) do
-      object = stringify_keys(object)
-      {:ok, object, meta}
-    end
-  end
-
   def validate(
         %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
         meta
@@ -225,10 +114,30 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
     end
   end
 
-  def validate(%{"type" => "Announce"} = object, meta) do
+  def validate(%{"type" => type} = object, meta)
+      when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
+      Event ChatMessage Question Audio Video Article Answer] do
+    validator =
+      case type do
+        "Accept" -> AcceptRejectValidator
+        "Reject" -> AcceptRejectValidator
+        "Follow" -> FollowValidator
+        "Update" -> UpdateValidator
+        "Like" -> LikeValidator
+        "EmojiReact" -> EmojiReactValidator
+        "Announce" -> AnnounceValidator
+        "Event" -> EventValidator
+        "ChatMessage" -> ChatMessageValidator
+        "Question" -> QuestionValidator
+        "Audio" -> AudioVideoValidator
+        "Video" -> AudioVideoValidator
+        "Article" -> ArticleNoteValidator
+        "Answer" -> AnswerValidator
+      end
+
     with {:ok, object} <-
            object
-           |> AnnounceValidator.cast_and_validate()
+           |> validator.cast_and_validate()
            |> Ecto.Changeset.apply_action(:insert) do
       object = stringify_keys(object)
       {:ok, object, meta}
index 73205fb6db500e31205bffc3d278b4da15b6620b..dac68d8e606899a8926bc42c46ce563e5dc8a215 100644 (file)
@@ -23,7 +23,8 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
         streaming_api: Pleroma.Web.Endpoint.websocket_url()
       },
       stats: Pleroma.Stats.get_stats(),
-      thumbnail: Pleroma.Web.base_url() <> Keyword.get(instance, :instance_thumbnail),
+      thumbnail:
+        URI.merge(Pleroma.Web.base_url(), Keyword.get(instance, :instance_thumbnail)) |> to_string,
       languages: ["en"],
       registrations: Keyword.get(instance, :registrations_open),
       approval_required: Keyword.get(instance, :account_approval_required),
diff --git a/mix.exs b/mix.exs
index ae74f50a3ce3939284e60349d9c221e32a78abef..fe5d9d9637b5990945c62886809b91368612f0af 100644 (file)
--- a/mix.exs
+++ b/mix.exs
@@ -38,7 +38,7 @@ defmodule Pleroma.Mixfile do
           include_executables_for: [:unix],
           applications: [ex_syslogger: :load, syslog: :load, eldap: :transient],
           steps: [:assemble, &put_otp_version/1, &copy_files/1, &copy_nginx_config/1],
-          config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, nil}]
+          config_providers: [{Pleroma.Config.ReleaseRuntimeProvider, []}]
         ]
       ]
     ]
diff --git a/priv/repo/migrations/20210401143153_user_notification_settings_fix.exs b/priv/repo/migrations/20210401143153_user_notification_settings_fix.exs
new file mode 100644 (file)
index 0000000..cf68f1b
--- /dev/null
@@ -0,0 +1,17 @@
+defmodule Pleroma.Repo.Migrations.UserNotificationSettingsFix do
+  use Ecto.Migration
+
+  def up do
+    execute(~s(UPDATE users
+    SET 
+      notification_settings = '{"followers": true, "follows": true, "non_follows": true, "non_followers": true}'::jsonb WHERE notification_settings IS NULL
+))
+
+    execute("ALTER TABLE users
+    ALTER COLUMN notification_settings SET NOT NULL")
+  end
+
+  def down do
+    :ok
+  end
+end
diff --git a/test/fixtures/config/temp.exported_from_db.secret.exs b/test/fixtures/config/temp.exported_from_db.secret.exs
new file mode 100644 (file)
index 0000000..64bee7f
--- /dev/null
@@ -0,0 +1,5 @@
+use Mix.Config
+
+config :pleroma, exported_config_merged: true
+
+config :pleroma, :first_setting, key: "new value"
diff --git a/test/pleroma/config/release_runtime_provider_test.exs b/test/pleroma/config/release_runtime_provider_test.exs
new file mode 100644 (file)
index 0000000..6578d32
--- /dev/null
@@ -0,0 +1,45 @@
+defmodule Pleroma.Config.ReleaseRuntimeProviderTest do
+  use ExUnit.Case, async: true
+
+  alias Pleroma.Config.ReleaseRuntimeProvider
+
+  describe "load/2" do
+    test "loads release defaults config and warns about non-existent runtime config" do
+      ExUnit.CaptureIO.capture_io(fn ->
+        merged = ReleaseRuntimeProvider.load([], [])
+        assert merged == Pleroma.Config.Holder.release_defaults()
+      end) =~
+        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
+    end
+
+    test "merged runtime config" do
+      merged =
+        ReleaseRuntimeProvider.load([], config_path: "test/fixtures/config/temp.secret.exs")
+
+      assert merged[:pleroma][:first_setting] == [key: "value", key2: [Pleroma.Repo]]
+      assert merged[:pleroma][:second_setting] == [key: "value2", key2: ["Activity"]]
+    end
+
+    test "merged exported config" do
+      ExUnit.CaptureIO.capture_io(fn ->
+        merged =
+          ReleaseRuntimeProvider.load([],
+            exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
+          )
+
+        assert merged[:pleroma][:exported_config_merged]
+      end) =~
+        "!!! Config path is not declared! Please ensure it exists and that PLEROMA_CONFIG_PATH is unset or points to an existing file"
+    end
+
+    test "runtime config is merged with exported config" do
+      merged =
+        ReleaseRuntimeProvider.load([],
+          config_path: "test/fixtures/config/temp.secret.exs",
+          exported_config_path: "test/fixtures/config/temp.exported_from_db.secret.exs"
+        )
+
+      assert merged[:pleroma][:first_setting] == [key2: [Pleroma.Repo], key: "new value"]
+    end
+  end
+end
diff --git a/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs b/test/pleroma/web/activity_pub/mrf/follow_bot_policy_test.exs
new file mode 100644 (file)
index 0000000..a615625
--- /dev/null
@@ -0,0 +1,126 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicyTest do
+  use Pleroma.DataCase, async: true
+
+  alias Pleroma.User
+  alias Pleroma.Web.ActivityPub.MRF.FollowBotPolicy
+
+  import Pleroma.Factory
+
+  describe "FollowBotPolicy" do
+    test "follows remote users" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, local: false)
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "Test post",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowBotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 1
+    end
+
+    test "does not follow users with #nobot in bio" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, %{local: false, bio: "go away bots! #nobot"})
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "I don't like follow bots",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowBotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+    end
+
+    test "does not follow local users" do
+      bot = insert(:user, actor_type: "Service")
+      local_user = insert(:user, local: true)
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [local_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "Hi I'm a local user",
+          "type" => "Note",
+          "attributedTo" => local_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => local_user.ap_id
+      }
+
+      refute User.following?(bot, local_user)
+
+      assert User.get_follow_requests(local_user) |> length == 0
+
+      FollowBotPolicy.filter(message)
+
+      assert User.get_follow_requests(local_user) |> length == 0
+    end
+
+    test "does not follow users requiring follower approval" do
+      bot = insert(:user, actor_type: "Service")
+      remote_user = insert(:user, %{local: false, is_locked: true})
+      clear_config([:mrf_follow_bot, :follower_nickname], bot.nickname)
+
+      message = %{
+        "@context" => "https://www.w3.org/ns/activitystreams",
+        "to" => [remote_user.follower_address],
+        "cc" => ["https://www.w3.org/ns/activitystreams#Public"],
+        "type" => "Create",
+        "object" => %{
+          "content" => "I don't like randos following me",
+          "type" => "Note",
+          "attributedTo" => remote_user.ap_id,
+          "inReplyTo" => nil
+        },
+        "actor" => remote_user.ap_id
+      }
+
+      refute User.following?(bot, remote_user)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+
+      FollowBotPolicy.filter(message)
+
+      assert User.get_follow_requests(remote_user) |> length == 0
+    end
+  end
+end
index 578a4c914074fda7524993cc306ffc431b1359aa..c39c1b1e19ed4fa62278c6135a97f246cdbd03e9 100644 (file)
@@ -1410,6 +1410,82 @@ defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do
                "need_reboot" => false
              }
     end
+
+    test "custom instance thumbnail", %{conn: conn} do
+      clear_config([:instance])
+
+      params = %{
+        "group" => ":pleroma",
+        "key" => ":instance",
+        "value" => [
+          %{
+            "tuple" => [
+              ":instance_thumbnail",
+              "https://example.com/media/new_thumbnail.jpg"
+            ]
+          }
+        ]
+      }
+
+      res =
+        assert conn
+               |> put_req_header("content-type", "application/json")
+               |> post("/api/pleroma/admin/config", %{"configs" => [params]})
+               |> json_response_and_validate_schema(200)
+
+      assert res == %{
+               "configs" => [
+                 %{
+                   "db" => [":instance_thumbnail"],
+                   "group" => ":pleroma",
+                   "key" => ":instance",
+                   "value" => params["value"]
+                 }
+               ],
+               "need_reboot" => false
+             }
+
+      _res =
+        assert conn
+               |> get("/api/v1/instance")
+               |> json_response_and_validate_schema(200)
+
+      assert res = %{"thumbnail" => "https://example.com/media/new_thumbnail.jpg"}
+    end
+
+    test "Concurrent Limiter", %{conn: conn} do
+      clear_config([ConcurrentLimiter])
+
+      params = %{
+        "group" => ":pleroma",
+        "key" => "ConcurrentLimiter",
+        "value" => [
+          %{
+            "tuple" => [
+              "Pleroma.Web.RichMedia.Helpers",
+              [
+                %{"tuple" => [":max_running", 6]},
+                %{"tuple" => [":max_waiting", 6]}
+              ]
+            ]
+          },
+          %{
+            "tuple" => [
+              "Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy",
+              [
+                %{"tuple" => [":max_running", 7]},
+                %{"tuple" => [":max_waiting", 7]}
+              ]
+            ]
+          }
+        ]
+      }
+
+      assert conn
+             |> put_req_header("content-type", "application/json")
+             |> post("/api/pleroma/admin/config", %{"configs" => [params]})
+             |> json_response_and_validate_schema(200)
+    end
   end
 
   describe "GET /api/pleroma/admin/config/descriptions" do