Merge branch 'ffmpeg-imagemagick-dependencies-documentation' into 'develop'
authorlain <lain@soykaf.club>
Tue, 29 Sep 2020 14:48:30 +0000 (14:48 +0000)
committerlain <lain@soykaf.club>
Tue, 29 Sep 2020 14:48:30 +0000 (14:48 +0000)
`ffmpeg` / `ImageMagick` handling as optional dependencies

See merge request pleroma/pleroma!3031

25 files changed:
CHANGELOG.md
docs/API/admin_api.md
docs/administration/CLI_tasks/email.md
docs/administration/CLI_tasks/user.md
lib/mix/tasks/pleroma/email.ex
lib/mix/tasks/pleroma/relay.ex
lib/mix/tasks/pleroma/user.ex
lib/pleroma/config/deprecation_warnings.ex
lib/pleroma/user.ex
lib/pleroma/user/query.ex
lib/pleroma/user/search.ex
lib/pleroma/web/activity_pub/relay.ex
lib/pleroma/web/activity_pub/transmogrifier.ex
lib/pleroma/web/admin_api/controllers/relay_controller.ex
lib/pleroma/web/api_spec/operations/admin/relay_operation.ex
priv/repo/migrations/20200925065249_make_user_ids_ci.exs [new file with mode: 0644]
priv/repo/migrations/20200928145912_revert_citext_change.exs [new file with mode: 0644]
test/config/deprecation_warnings_test.exs
test/tasks/email_test.exs
test/tasks/relay_test.exs
test/tasks/user_test.exs
test/user_search_test.exs
test/web/activity_pub/mrf/mrf_test.exs
test/web/activity_pub/relay_test.exs
test/web/activity_pub/transmogrifier/question_handling_test.exs

index f115851137c35fb83c9b8dfe5e94397b8c121d7d..e02e843011fae4ca83dddaa1ebcfc9979cabe3c6 100644 (file)
@@ -5,8 +5,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
 
 ## Unreleased
 
+### Added
+- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`)
+- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`)
+- Mix task option for force-unfollowing relays
+
 ### Changed
 
+- Search: Users are now findable by their urls.
 - Renamed `:await_up_timeout` in `:connections_pool` namespace to `:connect_timeout`, old name is deprecated.
 - Renamed `:timeout` in `pools` namespace to `:recv_timeout`, old name is deprecated.
 - The `discoverable` field in the `User` struct will now add a NOINDEX metatag to profile pages when false.
index 7992db58f055246f561dfdda2ede5e52889774f6..7bf13daef5f5251f4119081e0b7ad4e98fb61ab7 100644 (file)
@@ -349,9 +349,9 @@ Response:
 
 ### Unfollow a Relay
 
-Params:
-
-* `relay_url`
+Params:
+  - `relay_url`
+  - *optional* `force`: forcefully unfollow a relay even when the relay is not available. (default is `false`)
 
 Response:
 
index 00d2e74f83d9374b242cf406e8f0b0ea3110eff8..d9aa0e71ba1b433cebb716f51d8ec379916af3f8 100644 (file)
@@ -1,4 +1,4 @@
-# Managing emails
+# EMail administration tasks
 
 {! backend/administration/CLI_tasks/general_cli_task_info.include !}
 
@@ -30,3 +30,17 @@ Example:
     ```sh
     mix pleroma.email test --to root@example.org
     ```
+
+## Send confirmation emails to all unconfirmed user accounts
+
+=== "OTP"
+
+    ```sh
+     ./bin/pleroma_ctl email send_confirmation_mails
+    ```
+
+=== "From Source"
+
+    ```sh
+    mix pleroma.email send_confirmation_mails
+    ```
index 3e7f028ba1db471498eae190b0c38fab2196f74d..c64ed4f223b8cd5278f22d17245c58a3bbd32d43 100644 (file)
     ```
 
 ### Options
+- `--admin`/`--no-admin` - whether the user should be an admin
+- `--confirmed`/`--no-confirmed` - whether the user account is confirmed
 - `--locked`/`--no-locked` - whether the user should be locked
 - `--moderator`/`--no-moderator` - whether the user should be a moderator
-- `--admin`/`--no-admin` - whether the user should be an admin
 
 ## Add tags to a user
 
     ```sh
     mix pleroma.user toggle_confirmed <nickname>
     ```
+
+## Set confirmation status for all regular active users
+*Admins and moderators are excluded*
+
+=== "OTP"
+
+    ```sh
+     ./bin/pleroma_ctl user confirm_all
+    ```
+
+=== "From Source"
+
+    ```sh
+    mix pleroma.user confirm_all
+    ```
+
+## Revoke confirmation status for all regular active users
+*Admins and moderators are excluded*
+
+=== "OTP"
+
+    ```sh
+     ./bin/pleroma_ctl user unconfirm_all
+    ```
+
+=== "From Source"
+
+    ```sh
+    mix pleroma.user unconfirm_all
+    ```
index d3fac6ec8f3420da3ec0b765932b49ba432f42e6..9972cb9880d590acb293903a595a346e93e9ac8b 100644 (file)
@@ -2,11 +2,11 @@ defmodule Mix.Tasks.Pleroma.Email do
   use Mix.Task
   import Mix.Pleroma
 
-  @shortdoc "Simple Email test"
+  @shortdoc "Email administrative tasks"
   @moduledoc File.read!("docs/administration/CLI_tasks/email.md")
 
   def run(["test" | args]) do
-    Mix.Pleroma.start_pleroma()
+    start_pleroma()
 
     {options, [], []} =
       OptionParser.parse(
@@ -21,4 +21,20 @@ defmodule Mix.Tasks.Pleroma.Email do
 
     shell_info("Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}")
   end
+
+  def run(["resend_confirmation_emails"]) do
+    start_pleroma()
+
+    shell_info("Sending emails to all unconfirmed users")
+
+    Pleroma.User.Query.build(%{
+      local: true,
+      deactivated: false,
+      confirmation_pending: true,
+      invisible: false
+    })
+    |> Pleroma.Repo.chunk_stream(500)
+    |> Stream.each(&Pleroma.User.try_send_confirmation_email(&1))
+    |> Stream.run()
+  end
 end
index a6d8d6c1c872e35a3c5df60bd8a4a4bdc92ad7e5..bb808ca47dce09e903147dc655b1ec77cf4a2b8e 100644 (file)
@@ -21,10 +21,19 @@ defmodule Mix.Tasks.Pleroma.Relay do
     end
   end
 
-  def run(["unfollow", target]) do
+  def run(["unfollow", target | rest]) do
     start_pleroma()
 
-    with {:ok, _activity} <- Relay.unfollow(target) do
+    {options, [], []} =
+      OptionParser.parse(
+        rest,
+        strict: [force: :boolean],
+        aliases: [f: :force]
+      )
+
+    force = Keyword.get(options, :force, false)
+
+    with {:ok, _activity} <- Relay.unfollow(target, %{force: force}) do
       # put this task to sleep to allow the genserver to push out the messages
       :timer.sleep(500)
     else
index b20c49d89538db746214e7b6b17256995d3879c8..e062628047b402d192a0a4be3cd41e1f5020c7ae 100644 (file)
@@ -196,17 +196,24 @@ defmodule Mix.Tasks.Pleroma.User do
       OptionParser.parse(
         rest,
         strict: [
-          moderator: :boolean,
           admin: :boolean,
-          locked: :boolean
+          confirmed: :boolean,
+          locked: :boolean,
+          moderator: :boolean
         ]
       )
 
     with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
       user =
-        case Keyword.get(options, :moderator) do
+        case Keyword.get(options, :admin) do
           nil -> user
-          value -> set_moderator(user, value)
+          value -> set_admin(user, value)
+        end
+
+      user =
+        case Keyword.get(options, :confirmed) do
+          nil -> user
+          value -> set_confirmed(user, value)
         end
 
       user =
@@ -216,9 +223,9 @@ defmodule Mix.Tasks.Pleroma.User do
         end
 
       _user =
-        case Keyword.get(options, :admin) do
+        case Keyword.get(options, :moderator) do
           nil -> user
-          value -> set_admin(user, value)
+          value -> set_moderator(user, value)
         end
     else
       _ ->
@@ -353,6 +360,42 @@ defmodule Mix.Tasks.Pleroma.User do
     end
   end
 
+  def run(["confirm_all"]) do
+    start_pleroma()
+
+    Pleroma.User.Query.build(%{
+      local: true,
+      deactivated: false,
+      is_moderator: false,
+      is_admin: false,
+      invisible: false
+    })
+    |> Pleroma.Repo.chunk_stream(500, :batches)
+    |> Stream.each(fn users ->
+      users
+      |> Enum.each(fn user -> User.need_confirmation(user, false) end)
+    end)
+    |> Stream.run()
+  end
+
+  def run(["unconfirm_all"]) do
+    start_pleroma()
+
+    Pleroma.User.Query.build(%{
+      local: true,
+      deactivated: false,
+      is_moderator: false,
+      is_admin: false,
+      invisible: false
+    })
+    |> Pleroma.Repo.chunk_stream(500, :batches)
+    |> Stream.each(fn users ->
+      users
+      |> Enum.each(fn user -> User.need_confirmation(user, true) end)
+    end)
+    |> Stream.run()
+  end
+
   def run(["sign_out", nickname]) do
     start_pleroma()
 
@@ -410,4 +453,11 @@ defmodule Mix.Tasks.Pleroma.User do
     shell_info("Locked status of #{user.nickname}: #{user.locked}")
     user
   end
+
+  defp set_confirmed(user, value) do
+    {:ok, user} = User.need_confirmation(user, !value)
+
+    shell_info("Confirmation pending status of #{user.nickname}: #{user.confirmation_pending}")
+    user
+  end
 end
index 98c4dc9c8e1dad2abc8891ebe8d6074553f9a9d4..4ba6eaa77e0e86ad90a8c11a8dd058720ff29b16 100644 (file)
@@ -33,34 +33,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
     end
   end
 
-  def mrf_user_allowlist do
-    config = Config.get(:mrf_user_allowlist)
-
-    if config && Enum.any?(config, fn {k, _} -> is_atom(k) end) do
-      rewritten =
-        Enum.reduce(Config.get(:mrf_user_allowlist), Map.new(), fn {k, v}, acc ->
-          Map.put(acc, to_string(k), v)
-        end)
-
-      Config.put(:mrf_user_allowlist, rewritten)
-
-      Logger.error("""
-      !!!DEPRECATION WARNING!!!
-      As of Pleroma 2.0.7, the `mrf_user_allowlist` setting changed of format.
-      Pleroma 2.1 will remove support for the old format. Please change your configuration to match this:
-
-      config :pleroma, :mrf_user_allowlist, #{inspect(rewritten, pretty: true)}
-      """)
-
-      :error
-    else
-      :ok
-    end
-  end
-
   def warn do
     with :ok <- check_hellthread_threshold(),
-         :ok <- mrf_user_allowlist(),
          :ok <- check_old_mrf_config(),
          :ok <- check_media_proxy_whitelist_config(),
          :ok <- check_welcome_message_config(),
@@ -83,9 +57,9 @@ defmodule Pleroma.Config.DeprecationWarnings do
     if use_old_config do
       Logger.error("""
       !!!DEPRECATION WARNING!!!
-      Your config is using the old namespace for Welcome messages configuration. You need to change to the new namespace:
-      \n* `config :pleroma, :instance, welcome_user_nickname` is now `config :pleroma, :welcome, :direct_message, :sender_nickname`
-      \n* `config :pleroma, :instance, welcome_message` is now `config :pleroma, :welcome, :direct_message, :message`
+      Your config is using the old namespace for Welcome messages configuration. You need to convert to the new namespace. e.g.,
+      \n* `config :pleroma, :instance, welcome_user_nickname` and `config :pleroma, :instance, welcome_message` are now equal to:
+      \n* `config :pleroma, :welcome, direct_message: [enabled: true, sender_nickname: "NICKNAME", message: "Your welcome message"]`"
       """)
 
       :error
@@ -148,7 +122,7 @@ defmodule Pleroma.Config.DeprecationWarnings do
     if timeout = pool_config[:await_up_timeout] do
       Logger.warn("""
       !!!DEPRECATION WARNING!!!
-      Your config is using old setting name `await_up_timeout` instead of `connect_timeout`. Setting should work for now, but you are advised to change format to scheme with port to prevent possible issues later.
+      Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`. Please change to `config :pleroma, :connections_pool, connect_timeout` to ensure compatibility with future releases.
       """)
 
       Config.put(:connections_pool, Keyword.put_new(pool_config, :connect_timeout, timeout))
index 410c9cbac1a67cd78fd0c13a6098c66e16262d4e..71ace1c34a51afc3ed93c22fd1e7e9b6126296ad 100644 (file)
@@ -813,7 +813,8 @@ defmodule Pleroma.User do
   def send_welcome_email(_), do: {:ok, :noop}
 
   @spec try_send_confirmation_email(User.t()) :: {:ok, :enqueued | :noop}
-  def try_send_confirmation_email(%User{confirmation_pending: true} = user) do
+  def try_send_confirmation_email(%User{confirmation_pending: true, email: email} = user)
+      when is_binary(email) do
     if Config.get([:instance, :account_activation_required]) do
       send_confirmation_email(user)
       {:ok, :enqueued}
@@ -914,9 +915,7 @@ defmodule Pleroma.User do
         FollowingRelationship.unfollow(follower, followed)
         {:ok, followed} = update_follower_count(followed)
 
-        {:ok, follower} =
-          follower
-          |> update_following_count()
+        {:ok, follower} = update_following_count(follower)
 
         {:ok, follower, followed}
 
@@ -2071,6 +2070,13 @@ defmodule Pleroma.User do
     Enum.map(users, &toggle_confirmation/1)
   end
 
+  @spec need_confirmation(User.t(), boolean()) :: {:ok, User.t()} | {:error, Changeset.t()}
+  def need_confirmation(%User{} = user, bool) do
+    user
+    |> confirmation_changeset(need_confirmation: bool)
+    |> update_and_set_cache()
+  end
+
   def get_mascot(%{mascot: %{} = mascot}) when not is_nil(mascot) do
     mascot
   end
index 193b90d9d65e077d0b20d789afd843f62ff85758..2440bf890c1adf275b7341019f0e4738eb0bca46 100644 (file)
@@ -110,12 +110,12 @@ defmodule Pleroma.User.Query do
     where(query, [u], fragment("? && ?", u.tags, ^tags))
   end
 
-  defp compose_query({:is_admin, _}, query) do
-    where(query, [u], u.is_admin)
+  defp compose_query({:is_admin, bool}, query) do
+    where(query, [u], u.is_admin == ^bool)
   end
 
-  defp compose_query({:is_moderator, _}, query) do
-    where(query, [u], u.is_moderator)
+  defp compose_query({:is_moderator, bool}, query) do
+    where(query, [u], u.is_moderator == ^bool)
   end
 
   defp compose_query({:super_users, _}, query) do
@@ -148,6 +148,10 @@ defmodule Pleroma.User.Query do
     where(query, [u], u.deactivated == ^true)
   end
 
+  defp compose_query({:confirmation_pending, bool}, query) do
+    where(query, [u], u.confirmation_pending == ^bool)
+  end
+
   defp compose_query({:need_approval, _}, query) do
     where(query, [u], u.approval_pending)
   end
index b8c6486729e04ab920c89ec9a9f8151f147eba57..03f2c552f2f29b82cf20909d2d830ea284f1dc85 100644 (file)
@@ -3,8 +3,10 @@
 # SPDX-License-Identifier: AGPL-3.0-only
 
 defmodule Pleroma.User.Search do
+  alias Pleroma.EctoType.ActivityPub.ObjectValidators.Uri, as: UriType
   alias Pleroma.Pagination
   alias Pleroma.User
+
   import Ecto.Query
 
   @limit 20
@@ -19,16 +21,46 @@ defmodule Pleroma.User.Search do
 
     query_string = format_query(query_string)
 
-    maybe_resolve(resolve, for_user, query_string)
+    # If this returns anything, it should bounce to the top
+    maybe_resolved = maybe_resolve(resolve, for_user, query_string)
+
+    top_user_ids =
+      []
+      |> maybe_add_resolved(maybe_resolved)
+      |> maybe_add_ap_id_match(query_string)
+      |> maybe_add_uri_match(query_string)
 
     results =
       query_string
-      |> search_query(for_user, following)
+      |> search_query(for_user, following, top_user_ids)
       |> Pagination.fetch_paginated(%{"offset" => offset, "limit" => result_limit}, :offset)
 
     results
   end
 
+  defp maybe_add_resolved(list, {:ok, %User{} = user}) do
+    [user.id | list]
+  end
+
+  defp maybe_add_resolved(list, _), do: list
+
+  defp maybe_add_ap_id_match(list, query) do
+    if user = User.get_cached_by_ap_id(query) do
+      [user.id | list]
+    else
+      list
+    end
+  end
+
+  defp maybe_add_uri_match(list, query) do
+    with {:ok, query} <- UriType.cast(query),
+         %User{} = user <- Pleroma.Repo.get_by(User, uri: query) do
+      [user.id | list]
+    else
+      _ -> list
+    end
+  end
+
   defp format_query(query_string) do
     # Strip the beginning @ off if there is a query
     query_string = String.trim_leading(query_string, "@")
@@ -47,7 +79,7 @@ defmodule Pleroma.User.Search do
     end
   end
 
-  defp search_query(query_string, for_user, following) do
+  defp search_query(query_string, for_user, following, top_user_ids) do
     for_user
     |> base_query(following)
     |> filter_blocked_user(for_user)
@@ -56,13 +88,20 @@ defmodule Pleroma.User.Search do
     |> filter_internal_users()
     |> filter_blocked_domains(for_user)
     |> fts_search(query_string)
+    |> select_top_users(top_user_ids)
     |> trigram_rank(query_string)
-    |> boost_search_rank(for_user)
+    |> boost_search_rank(for_user, top_user_ids)
     |> subquery()
     |> order_by(desc: :search_rank)
     |> maybe_restrict_local(for_user)
   end
 
+  defp select_top_users(query, top_user_ids) do
+    from(u in query,
+      or_where: u.id in ^top_user_ids
+    )
+  end
+
   defp fts_search(query, query_string) do
     query_string = to_tsquery(query_string)
 
@@ -180,7 +219,7 @@ defmodule Pleroma.User.Search do
 
   defp local_domain, do: Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host])
 
-  defp boost_search_rank(query, %User{} = for_user) do
+  defp boost_search_rank(query, %User{} = for_user, top_user_ids) do
     friends_ids = User.get_friends_ids(for_user)
     followers_ids = User.get_followers_ids(for_user)
 
@@ -192,6 +231,7 @@ defmodule Pleroma.User.Search do
              CASE WHEN (?) THEN (?) * 1.5
              WHEN (?) THEN (?) * 1.3
              WHEN (?) THEN (?) * 1.1
+             WHEN (?) THEN 9001
              ELSE (?) END
             """,
             u.id in ^friends_ids and u.id in ^followers_ids,
@@ -200,11 +240,26 @@ defmodule Pleroma.User.Search do
             u.search_rank,
             u.id in ^followers_ids,
             u.search_rank,
+            u.id in ^top_user_ids,
             u.search_rank
           )
       }
     )
   end
 
-  defp boost_search_rank(query, _for_user), do: query
+  defp boost_search_rank(query, _for_user, top_user_ids) do
+    from(u in subquery(query),
+      select_merge: %{
+        search_rank:
+          fragment(
+            """
+             CASE WHEN (?) THEN 9001
+             ELSE (?) END
+            """,
+            u.id in ^top_user_ids,
+            u.search_rank
+          )
+      }
+    )
+  end
 end
index b65710a941d1b92895a28dd58cf644fcf0d3de4f..6606e1780ecff540b9a0df15679b93471e83cffa 100644 (file)
@@ -30,12 +30,16 @@ defmodule Pleroma.Web.ActivityPub.Relay do
     end
   end
 
-  @spec unfollow(String.t()) :: {:ok, Activity.t()} | {:error, any()}
-  def unfollow(target_instance) do
+  @spec unfollow(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
+  def unfollow(target_instance, opts \\ %{}) do
     with %User{} = local_user <- get_actor(),
-         {:ok, %User{} = target_user} <- User.get_or_fetch_by_ap_id(target_instance),
+         {:ok, target_user} <- fetch_target_user(target_instance, opts),
          {:ok, activity} <- ActivityPub.unfollow(local_user, target_user) do
-      User.unfollow(local_user, target_user)
+      case target_user.id do
+        nil -> User.update_following_count(local_user)
+        _ -> User.unfollow(local_user, target_user)
+      end
+
       Logger.info("relay: unfollowed instance: #{target_instance}: id=#{activity.data["id"]}")
       {:ok, activity}
     else
@@ -43,6 +47,14 @@ defmodule Pleroma.Web.ActivityPub.Relay do
     end
   end
 
+  defp fetch_target_user(ap_id, opts) do
+    case {opts[:force], User.get_or_fetch_by_ap_id(ap_id)} do
+      {_, {:ok, %User{} = user}} -> {:ok, user}
+      {true, _} -> {:ok, %User{ap_id: ap_id}}
+      {_, error} -> error
+    end
+  end
+
   @spec publish(any()) :: {:ok, Activity.t()} | {:error, any()}
   def publish(%Activity{data: %{"type" => "Create"}} = activity) do
     with %User{} = user <- get_actor(),
index aa6a694639be8bd5f4dd276ddc6f2e855c7ba433..d7dd9fe6becf8ddab8088e8845a64fc34f2e1c9c 100644 (file)
@@ -515,15 +515,19 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
   end
 
   def handle_incoming(
-        %{"type" => "Create", "object" => %{"type" => objtype}} = data,
+        %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
         _options
       )
       when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do
     data = Map.put(data, "object", strip_internal_fields(data["object"]))
 
     with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
+         nil <- Activity.get_create_by_object_ap_id(obj_id),
          {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
       {:ok, activity}
+    else
+      %Activity{} = activity -> {:ok, activity}
+      e -> e
     end
   end
 
index 95d06dde749ba5f3f898c05fe25386ed3cd72043..6c19f09f7647fe28a5a121883b69de9d02174c89 100644 (file)
@@ -33,11 +33,7 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
 
   def follow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
     with {:ok, _message} <- Relay.follow(target) do
-      ModerationLog.insert_log(%{
-        action: "relay_follow",
-        actor: admin,
-        target: target
-      })
+      ModerationLog.insert_log(%{action: "relay_follow", actor: admin, target: target})
 
       json(conn, %{actor: target, followed_back: target in Relay.following()})
     else
@@ -48,13 +44,9 @@ defmodule Pleroma.Web.AdminAPI.RelayController do
     end
   end
 
-  def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target}} = conn, _) do
-    with {:ok, _message} <- Relay.unfollow(target) do
-      ModerationLog.insert_log(%{
-        action: "relay_unfollow",
-        actor: admin,
-        target: target
-      })
+  def unfollow(%{assigns: %{user: admin}, body_params: %{relay_url: target} = params} = conn, _) do
+    with {:ok, _message} <- Relay.unfollow(target, %{force: params[:force]}) do
+      ModerationLog.insert_log(%{action: "relay_unfollow", actor: admin, target: target})
 
       json(conn, target)
     else
index e06b2d1645cc8af739de6a3937c45184e336e75e..f754bb9f5cd0730d3dffe3d28b304c8d210e5538 100644 (file)
@@ -56,7 +56,7 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
       operationId: "AdminAPI.RelayController.unfollow",
       security: [%{"oAuth" => ["write:follows"]}],
       parameters: admin_api_params(),
-      requestBody: request_body("Parameters", relay_url()),
+      requestBody: request_body("Parameters", relay_unfollow()),
       responses: %{
         200 =>
           Operation.response("Status", "application/json", %Schema{
@@ -91,4 +91,14 @@ defmodule Pleroma.Web.ApiSpec.Admin.RelayOperation do
       }
     }
   end
+
+  defp relay_unfollow do
+    %Schema{
+      type: :object,
+      properties: %{
+        relay_url: %Schema{type: :string, format: :uri},
+        force: %Schema{type: :boolean, default: false}
+      }
+    }
+  end
 end
diff --git a/priv/repo/migrations/20200925065249_make_user_ids_ci.exs b/priv/repo/migrations/20200925065249_make_user_ids_ci.exs
new file mode 100644 (file)
index 0000000..8ea0f2c
--- /dev/null
@@ -0,0 +1,9 @@
+defmodule Pleroma.Repo.Migrations.MakeUserIdsCI do
+  use Ecto.Migration
+
+  def change do
+    # Migration retired, see
+    # https://git.pleroma.social/pleroma/pleroma/-/issues/2188
+    :noop
+  end
+end
diff --git a/priv/repo/migrations/20200928145912_revert_citext_change.exs b/priv/repo/migrations/20200928145912_revert_citext_change.exs
new file mode 100644 (file)
index 0000000..ab232f6
--- /dev/null
@@ -0,0 +1,11 @@
+defmodule Pleroma.Repo.Migrations.RevertCitextChange do
+  use Ecto.Migration
+
+  def change do
+    alter table(:users) do
+      modify(:uri, :text)
+    end
+
+    create_if_not_exists(unique_index(:users, :uri))
+  end
+end
index e22052404c899bdc3b92618361d7ae7b93239ea4..f81a7b5807dd1dbd10ce729b35dc6342127851cd 100644 (file)
@@ -1,5 +1,5 @@
 defmodule Pleroma.Config.DeprecationWarningsTest do
-  use ExUnit.Case, async: true
+  use ExUnit.Case
   use Pleroma.Tests.Helpers
 
   import ExUnit.CaptureLog
@@ -66,6 +66,30 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
            end) =~ "Your config is using old format (only domain) for MediaProxy whitelist option"
   end
 
+  test "check_welcome_message_config/0" do
+    clear_config([:instance, :welcome_user_nickname], "LainChan")
+
+    assert capture_log(fn ->
+             DeprecationWarnings.check_welcome_message_config()
+           end) =~ "Your config is using the old namespace for Welcome messages configuration."
+  end
+
+  test "check_hellthread_threshold/0" do
+    clear_config([:mrf_hellthread, :threshold], 16)
+
+    assert capture_log(fn ->
+             DeprecationWarnings.check_hellthread_threshold()
+           end) =~ "You are using the old configuration mechanism for the hellthread filter."
+  end
+
+  test "check_activity_expiration_config/0" do
+    clear_config([Pleroma.ActivityExpiration, :enabled], true)
+
+    assert capture_log(fn ->
+             DeprecationWarnings.check_activity_expiration_config()
+           end) =~ "Your config is using old namespace for activity expiration configuration."
+  end
+
   describe "check_gun_pool_options/0" do
     test "await_up_timeout" do
       config = Config.get(:connections_pool)
@@ -74,7 +98,7 @@ defmodule Pleroma.Config.DeprecationWarningsTest do
       assert capture_log(fn ->
                DeprecationWarnings.check_gun_pool_options()
              end) =~
-               "Your config is using old setting name `await_up_timeout` instead of `connect_timeout`"
+               "Your config is using old setting `config :pleroma, :connections_pool, await_up_timeout`."
     end
 
     test "pool timeout" do
index c3af7ef68ab3a2c7dc9b91844257660f59795789..5393e3573d9f2bf1d3fbab1dbc414702210d57be 100644 (file)
@@ -6,6 +6,8 @@ defmodule Mix.Tasks.Pleroma.EmailTest do
   alias Pleroma.Config
   alias Pleroma.Tests.ObanHelpers
 
+  import Pleroma.Factory
+
   setup_all do
     Mix.shell(Mix.Shell.Process)
 
@@ -17,6 +19,7 @@ defmodule Mix.Tasks.Pleroma.EmailTest do
   end
 
   setup do: clear_config([Pleroma.Emails.Mailer, :enabled], true)
+  setup do: clear_config([:instance, :account_activation_required], true)
 
   describe "pleroma.email test" do
     test "Sends test email with no given address" do
@@ -50,5 +53,71 @@ defmodule Mix.Tasks.Pleroma.EmailTest do
         html_body: ~r/a test email was requested./i
       )
     end
+
+    test "Sends confirmation emails" do
+      local_user1 =
+        insert(:user, %{
+          confirmation_pending: true,
+          confirmation_token: "mytoken",
+          deactivated: false,
+          email: "local1@pleroma.com",
+          local: true
+        })
+
+      local_user2 =
+        insert(:user, %{
+          confirmation_pending: true,
+          confirmation_token: "mytoken",
+          deactivated: false,
+          email: "local2@pleroma.com",
+          local: true
+        })
+
+      :ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"])
+
+      ObanHelpers.perform_all()
+
+      assert_email_sent(to: {local_user1.name, local_user1.email})
+      assert_email_sent(to: {local_user2.name, local_user2.email})
+    end
+
+    test "Does not send confirmation email to inappropriate users" do
+      # confirmed user
+      insert(:user, %{
+        confirmation_pending: false,
+        confirmation_token: "mytoken",
+        deactivated: false,
+        email: "confirmed@pleroma.com",
+        local: true
+      })
+
+      # remote user
+      insert(:user, %{
+        deactivated: false,
+        email: "remote@not-pleroma.com",
+        local: false
+      })
+
+      # deactivated user =
+      insert(:user, %{
+        deactivated: true,
+        email: "deactivated@pleroma.com",
+        local: false
+      })
+
+      # invisible user
+      insert(:user, %{
+        deactivated: false,
+        email: "invisible@pleroma.com",
+        local: true,
+        invisible: true
+      })
+
+      :ok = Mix.Tasks.Pleroma.Email.run(["resend_confirmation_emails"])
+
+      ObanHelpers.perform_all()
+
+      refute_email_sent()
+    end
   end
 end
index e5225b64c46c2ae7834900498a895901bdb7f178..cf48e7dda84dbde167fec1c9e35114fd777743eb 100644 (file)
@@ -81,6 +81,80 @@ defmodule Mix.Tasks.Pleroma.RelayTest do
       assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
       refute "#{target_instance}/followers" in User.following(local_user)
     end
+
+    test "unfollow when relay is dead" do
+      user = insert(:user)
+      target_instance = user.ap_id
+
+      Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
+
+      %User{ap_id: follower_id} = local_user = Relay.get_actor()
+      target_user = User.get_cached_by_ap_id(target_instance)
+      follow_activity = Utils.fetch_latest_follow(local_user, target_user)
+      User.follow(local_user, target_user)
+
+      assert "#{target_instance}/followers" in User.following(local_user)
+
+      Tesla.Mock.mock(fn %{method: :get, url: ^target_instance} ->
+        %Tesla.Env{status: 404}
+      end)
+
+      Pleroma.Repo.delete(user)
+      Cachex.clear(:user_cache)
+
+      Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance])
+
+      cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
+      assert cancelled_activity.data["state"] == "accept"
+
+      assert [] ==
+               ActivityPub.fetch_activities(
+                 [],
+                 %{
+                   type: "Undo",
+                   actor_id: follower_id,
+                   skip_preload: true,
+                   invisible_actors: true
+                 }
+               )
+    end
+
+    test "force unfollow when relay is dead" do
+      user = insert(:user)
+      target_instance = user.ap_id
+
+      Mix.Tasks.Pleroma.Relay.run(["follow", target_instance])
+
+      %User{ap_id: follower_id} = local_user = Relay.get_actor()
+      target_user = User.get_cached_by_ap_id(target_instance)
+      follow_activity = Utils.fetch_latest_follow(local_user, target_user)
+      User.follow(local_user, target_user)
+
+      assert "#{target_instance}/followers" in User.following(local_user)
+
+      Tesla.Mock.mock(fn %{method: :get, url: ^target_instance} ->
+        %Tesla.Env{status: 404}
+      end)
+
+      Pleroma.Repo.delete(user)
+      Cachex.clear(:user_cache)
+
+      Mix.Tasks.Pleroma.Relay.run(["unfollow", target_instance, "--force"])
+
+      cancelled_activity = Activity.get_by_ap_id(follow_activity.data["id"])
+      assert cancelled_activity.data["state"] == "cancelled"
+
+      [undo_activity] =
+        ActivityPub.fetch_activities(
+          [],
+          %{type: "Undo", actor_id: follower_id, skip_preload: true, invisible_actors: true}
+        )
+
+      assert undo_activity.data["type"] == "Undo"
+      assert undo_activity.data["actor"] == local_user.ap_id
+      assert undo_activity.data["object"]["id"] == cancelled_activity.data["id"]
+      refute "#{target_instance}/followers" in User.following(local_user)
+    end
   end
 
   describe "mix pleroma.relay list" do
index ce43a9cc72947f58e3dcff327b08ee8393735057..b8c423c48455590765345bbc15faac0d68c441f7 100644 (file)
@@ -225,47 +225,64 @@ defmodule Mix.Tasks.Pleroma.UserTest do
     test "All statuses set" do
       user = insert(:user)
 
-      Mix.Tasks.Pleroma.User.run(["set", user.nickname, "--moderator", "--admin", "--locked"])
+      Mix.Tasks.Pleroma.User.run([
+        "set",
+        user.nickname,
+        "--admin",
+        "--confirmed",
+        "--locked",
+        "--moderator"
+      ])
 
       assert_received {:mix_shell, :info, [message]}
-      assert message =~ ~r/Moderator status .* true/
+      assert message =~ ~r/Admin status .* true/
+
+      assert_received {:mix_shell, :info, [message]}
+      assert message =~ ~r/Confirmation pending .* false/
 
       assert_received {:mix_shell, :info, [message]}
       assert message =~ ~r/Locked status .* true/
 
       assert_received {:mix_shell, :info, [message]}
-      assert message =~ ~r/Admin status .* true/
+      assert message =~ ~r/Moderator status .* true/
 
       user = User.get_cached_by_nickname(user.nickname)
       assert user.is_moderator
       assert user.locked
       assert user.is_admin
+      refute user.confirmation_pending
     end
 
     test "All statuses unset" do
-      user = insert(:user, locked: true, is_moderator: true, is_admin: true)
+      user =
+        insert(:user, locked: true, is_moderator: true, is_admin: true, confirmation_pending: true)
 
       Mix.Tasks.Pleroma.User.run([
         "set",
         user.nickname,
-        "--no-moderator",
         "--no-admin",
-        "--no-locked"
+        "--no-confirmed",
+        "--no-locked",
+        "--no-moderator"
       ])
 
       assert_received {:mix_shell, :info, [message]}
-      assert message =~ ~r/Moderator status .* false/
+      assert message =~ ~r/Admin status .* false/
+
+      assert_received {:mix_shell, :info, [message]}
+      assert message =~ ~r/Confirmation pending .* true/
 
       assert_received {:mix_shell, :info, [message]}
       assert message =~ ~r/Locked status .* false/
 
       assert_received {:mix_shell, :info, [message]}
-      assert message =~ ~r/Admin status .* false/
+      assert message =~ ~r/Moderator status .* false/
 
       user = User.get_cached_by_nickname(user.nickname)
       refute user.is_moderator
       refute user.locked
       refute user.is_admin
+      assert user.confirmation_pending
     end
 
     test "no user to set status" do
@@ -554,4 +571,44 @@ defmodule Mix.Tasks.Pleroma.UserTest do
       assert message =~ "Could not change user tags"
     end
   end
+
+  describe "bulk confirm and unconfirm" do
+    test "confirm all" do
+      user1 = insert(:user, confirmation_pending: true)
+      user2 = insert(:user, confirmation_pending: true)
+
+      assert user1.confirmation_pending
+      assert user2.confirmation_pending
+
+      Mix.Tasks.Pleroma.User.run(["confirm_all"])
+
+      user1 = User.get_cached_by_nickname(user1.nickname)
+      user2 = User.get_cached_by_nickname(user2.nickname)
+
+      refute user1.confirmation_pending
+      refute user2.confirmation_pending
+    end
+
+    test "unconfirm all" do
+      user1 = insert(:user, confirmation_pending: false)
+      user2 = insert(:user, confirmation_pending: false)
+      admin = insert(:user, is_admin: true, confirmation_pending: false)
+      mod = insert(:user, is_moderator: true, confirmation_pending: false)
+
+      refute user1.confirmation_pending
+      refute user2.confirmation_pending
+
+      Mix.Tasks.Pleroma.User.run(["unconfirm_all"])
+
+      user1 = User.get_cached_by_nickname(user1.nickname)
+      user2 = User.get_cached_by_nickname(user2.nickname)
+      admin = User.get_cached_by_nickname(admin.nickname)
+      mod = User.get_cached_by_nickname(mod.nickname)
+
+      assert user1.confirmation_pending
+      assert user2.confirmation_pending
+      refute admin.confirmation_pending
+      refute mod.confirmation_pending
+    end
+  end
 end
index 8529ce6dbf324e921a05b7b26b8f5e53ca26b4c0..cc14e97415547bb1bbffbb58c263473e712dbf89 100644 (file)
@@ -17,6 +17,40 @@ defmodule Pleroma.UserSearchTest do
   describe "User.search" do
     setup do: clear_config([:instance, :limit_to_local_content])
 
+    test "returns a resolved user as the first result" do
+      Pleroma.Config.put([:instance, :limit_to_local_content], false)
+      user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"})
+      _user = insert(:user, %{nickname: "com_user"})
+
+      [first_user, _second_user] = User.search("https://lain.com/users/lain", resolve: true)
+
+      assert first_user.id == user.id
+    end
+
+    test "returns a user with matching ap_id as the first result" do
+      user = insert(:user, %{nickname: "no_relation", ap_id: "https://lain.com/users/lain"})
+      _user = insert(:user, %{nickname: "com_user"})
+
+      [first_user, _second_user] = User.search("https://lain.com/users/lain")
+
+      assert first_user.id == user.id
+    end
+
+    test "returns a user with matching uri as the first result" do
+      user =
+        insert(:user, %{
+          nickname: "no_relation",
+          ap_id: "https://lain.com/users/lain",
+          uri: "https://lain.com/@lain"
+        })
+
+      _user = insert(:user, %{nickname: "com_user"})
+
+      [first_user, _second_user] = User.search("https://lain.com/@lain")
+
+      assert first_user.id == user.id
+    end
+
     test "excludes invisible users from results" do
       user = insert(:user, %{nickname: "john t1000"})
       insert(:user, %{invisible: true, nickname: "john t800"})
index a63b254233fc2ecd9254c7adff3eb733488538b1..e82c8afa6b62d8b371148299a34f47fe9d9b1f88 100644 (file)
@@ -61,6 +61,8 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do
 
   describe "describe/0" do
     test "it works as expected with noop policy" do
+      clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.NoOpPolicy])
+
       expected = %{
         mrf_policies: ["NoOpPolicy"],
         exclusions: false
index 9d657ac4fcf84fdbe464b503e01451baeae7f005..3284980f75ca62170136203db24196b76bfd4374 100644 (file)
@@ -63,6 +63,46 @@ defmodule Pleroma.Web.ActivityPub.RelayTest do
       assert activity.data["to"] == [user.ap_id]
       refute "#{user.ap_id}/followers" in User.following(service_actor)
     end
+
+    test "force unfollow when target service is dead" do
+      user = insert(:user)
+      user_ap_id = user.ap_id
+      user_id = user.id
+
+      Tesla.Mock.mock(fn %{method: :get, url: ^user_ap_id} ->
+        %Tesla.Env{status: 404}
+      end)
+
+      service_actor = Relay.get_actor()
+      CommonAPI.follow(service_actor, user)
+      assert "#{user.ap_id}/followers" in User.following(service_actor)
+
+      assert Pleroma.Repo.get_by(
+               Pleroma.FollowingRelationship,
+               follower_id: service_actor.id,
+               following_id: user_id
+             )
+
+      Pleroma.Repo.delete(user)
+      Cachex.clear(:user_cache)
+
+      assert {:ok, %Activity{} = activity} = Relay.unfollow(user_ap_id, %{force: true})
+
+      assert refresh_record(service_actor).following_count == 0
+
+      refute Pleroma.Repo.get_by(
+               Pleroma.FollowingRelationship,
+               follower_id: service_actor.id,
+               following_id: user_id
+             )
+
+      assert activity.actor == "#{Pleroma.Web.Endpoint.url()}/relay"
+      assert user.ap_id in activity.recipients
+      assert activity.data["type"] == "Undo"
+      assert activity.data["actor"] == service_actor.ap_id
+      assert activity.data["to"] == [user_ap_id]
+      refute "#{user.ap_id}/followers" in User.following(service_actor)
+    end
   end
 
   describe "publish/1" do
index 74ee7954382592231a5471467336e05878f2c345..d2822ce75b3e9155f5381ca498b64f732e261607 100644 (file)
@@ -157,12 +157,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.QuestionHandlingTest do
            }
   end
 
-  test "returns an error if received a second time" do
+  test "returns same activity if received a second time" do
     data = File.read!("test/fixtures/mastodon-question-activity.json") |> Poison.decode!()
 
     assert {:ok, %Activity{local: false} = activity} = Transmogrifier.handle_incoming(data)
 
-    assert {:error, {:validate_object, {:error, _}}} = Transmogrifier.handle_incoming(data)
+    assert {:ok, ^activity} = Transmogrifier.handle_incoming(data)
   end
 
   test "accepts a Question with no content" do