Merge remote-tracking branch 'upstream/develop' into restrict-origin
authorAlex Gleason <alex@alexgleason.me>
Thu, 8 Oct 2020 22:24:09 +0000 (17:24 -0500)
committerAlex Gleason <alex@alexgleason.me>
Thu, 8 Oct 2020 22:24:09 +0000 (17:24 -0500)
1  2 
lib/pleroma/web/activity_pub/activity_pub.ex
lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex
test/web/admin_api/controllers/admin_api_controller_test.exs
test/web/mastodon_api/controllers/timeline_controller_test.exs

index 2ee7d38d7ee8a7123d4cedcbd0e502a514498819,eb44cffec14eddb5634a3973a1f6625fbcd14acb..42064b51f30df8cc1b50569c3d45746df7634b62
@@@ -5,7 -5,6 +5,6 @@@
  defmodule Pleroma.Web.ActivityPub.ActivityPub do
    alias Pleroma.Activity
    alias Pleroma.Activity.Ir.Topics
-   alias Pleroma.ActivityExpiration
    alias Pleroma.Config
    alias Pleroma.Constants
    alias Pleroma.Conversation
@@@ -66,7 -65,7 +65,7 @@@
  
    defp check_remote_limit(_), do: true
  
-   defp increase_note_count_if_public(actor, object) do
+   def increase_note_count_if_public(actor, object) do
      if is_public?(object), do: User.increase_note_count(actor), else: {:ok, actor}
    end
  
  
    defp increase_replies_count_if_reply(_create_data), do: :noop
  
-   defp increase_poll_votes_if_vote(%{
-          "object" => %{"inReplyTo" => reply_ap_id, "name" => name},
-          "type" => "Create",
-          "actor" => actor
-        }) do
-     Object.increase_vote_count(reply_ap_id, name, actor)
-   end
-   defp increase_poll_votes_if_vote(_create_data), do: :noop
-   @object_types ["ChatMessage"]
+   @object_types ~w[ChatMessage Question Answer Audio Video Event Article]
    @spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
    def persist(%{"type" => type} = object, meta) when type in @object_types do
      with {:ok, object} <- Object.create(object) do
               local: local,
               recipients: recipients,
               actor: object["actor"]
-            }) do
+            }),
+          # TODO: add tests for expired activities, when Note type will be supported in new pipeline
+          {:ok, _} <- maybe_create_activity_expiration(activity) do
        {:ok, activity, meta}
      end
    end
    def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
      with nil <- Activity.normalize(map),
           map <- lazy_put_activity_defaults(map, fake),
-          true <- bypass_actor_check || check_actor_is_active(map["actor"]),
-          {_, true} <- {:remote_limit_error, check_remote_limit(map)},
+          {_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])},
+          {_, true} <- {:remote_limit_pass, check_remote_limit(map)},
           {:ok, map} <- MRF.filter(map),
           {recipients, _, _} = get_recipients(map),
           {:fake, false, map, recipients} <- {:fake, fake, map, recipients},
           {:containment, :ok} <- {:containment, Containment.contain_child(map)},
-          {:ok, map, object} <- insert_full_object(map) do
-       {:ok, activity} =
-         %Activity{
-           data: map,
-           local: local,
-           actor: map["actor"],
-           recipients: recipients
-         }
-         |> Repo.insert()
-         |> maybe_create_activity_expiration()
+          {:ok, map, object} <- insert_full_object(map),
+          {:ok, activity} <- insert_activity_with_expiration(map, local, recipients) do
        # Splice in the child object if we have one.
        activity = Maps.put_if_present(activity, :object, object)
  
        %Activity{} = activity ->
          {:ok, activity}
  
+       {:actor_check, _} ->
+         {:error, false}
+       {:containment, _} = error ->
+         error
+       {:error, _} = error ->
+         error
        {:fake, true, map, recipients} ->
          activity = %Activity{
            data: map,
          Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
          {:ok, activity}
  
-       error ->
-         {:error, error}
+       {:remote_limit_pass, _} ->
+         {:error, :remote_limit}
+       {:reject, _} = e ->
+         {:error, e}
+     end
+   end
+   defp insert_activity_with_expiration(data, local, recipients) do
+     struct = %Activity{
+       data: data,
+       local: local,
+       actor: data["actor"],
+       recipients: recipients
+     }
+     with {:ok, activity} <- Repo.insert(struct) do
+       maybe_create_activity_expiration(activity)
      end
    end
  
      stream_out_participations(participations)
    end
  
-   defp maybe_create_activity_expiration({:ok, %{data: %{"expires_at" => expires_at}} = activity}) do
-     with {:ok, _} <- ActivityExpiration.create(activity, expires_at) do
+   defp maybe_create_activity_expiration(
+          %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity
+        ) do
+     with {:ok, _job} <-
+            Pleroma.Workers.PurgeExpiredActivity.enqueue(%{
+              activity_id: activity.id,
+              expires_at: expires_at
+            }) do
        {:ok, activity}
      end
    end
  
-   defp maybe_create_activity_expiration(result), do: result
+   defp maybe_create_activity_expiration(activity), do: {:ok, activity}
  
    defp create_or_bump_conversation(activity, actor) do
      with {:ok, conversation} <- Conversation.create_or_bump_for(activity),
      with {:ok, activity} <- insert(create_data, local, fake),
           {:fake, false, activity} <- {:fake, fake, activity},
           _ <- increase_replies_count_if_reply(create_data),
-          _ <- increase_poll_votes_if_vote(create_data),
           {:quick_insert, false, activity} <- {:quick_insert, quick_insert?, activity},
           {:ok, _actor} <- increase_note_count_if_public(actor, activity),
           _ <- notify_and_stream(activity),
      end
    end
  
-   @spec accept(map()) :: {:ok, Activity.t()} | {:error, any()}
-   def accept(params) do
-     accept_or_reject("Accept", params)
-   end
-   @spec reject(map()) :: {:ok, Activity.t()} | {:error, any()}
-   def reject(params) do
-     accept_or_reject("Reject", params)
-   end
-   @spec accept_or_reject(String.t(), map()) :: {:ok, Activity.t()} | {:error, any()}
-   defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
-     local = Map.get(params, :local, true)
-     activity_id = Map.get(params, :activity_id, nil)
-     data =
-       %{"to" => to, "type" => type, "actor" => actor.ap_id, "object" => object}
-       |> Maps.put_if_present("id", activity_id)
-     with {:ok, activity} <- insert(data, local),
-          _ <- notify_and_stream(activity),
-          :ok <- maybe_federate(activity) do
-       {:ok, activity}
-     end
-   end
    @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) ::
            {:ok, Activity.t()} | nil | {:error, any()}
    def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
    end
  
    defp restrict_replies(query, %{
-          reply_filtering_user: user,
+          reply_filtering_user: %User{} = user,
           reply_visibility: "self"
         }) do
      from(
    end
  
    defp restrict_replies(query, %{
-          reply_filtering_user: user,
+          reply_filtering_user: %User{} = user,
           reply_visibility: "following"
         }) do
      from(
        [activity, object] in query,
        where:
          fragment(
-           "?->>'inReplyTo' is null OR ? && array_remove(?, ?) OR ? = ?",
+           """
+           ?->>'type' != 'Create'     -- This isn't a Create      
+           OR ?->>'inReplyTo' is null -- this isn't a reply
+           OR ? && array_remove(?, ?) -- The recipient is us or one of our friends, 
+                                      -- unless they are the author (because authors 
+                                      -- are also part of the recipients). This leads
+                                      -- to a bug that self-replies by friends won't
+                                      -- show up.
+           OR ? = ?                   -- The actor is us
+           """,
+           activity.data,
            object.data,
            ^[user.ap_id | User.get_cached_user_friends_ap_ids(user)],
            activity.recipients,
      from(
        [activity, object: o] in query,
        where: fragment("not (? = ANY(?))", activity.actor, ^blocked_ap_ids),
-       where: fragment("not (? && ?)", activity.recipients, ^blocked_ap_ids),
+       where:
+         fragment(
+           "((not (? && ?)) or ? = ?)",
+           activity.recipients,
+           ^blocked_ap_ids,
+           activity.actor,
+           ^user.ap_id
+         ),
        where:
          fragment(
            "recipients_contain_blocked_domains(?, ?) = false",
  
    defp restrict_muted_reblogs(query, _), do: query
  
 -  defp restrict_instance(query, %{instance: instance}) do
 -    users =
 -      from(
 -        u in User,
 -        select: u.ap_id,
 -        where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
 -      )
 -      |> Repo.all()
 -
 -    from(activity in query, where: activity.actor in ^users)
 +  defp restrict_instance(query, %{instance: instance}) when is_binary(instance) do
 +    from(
 +      activity in query,
 +      where: fragment("split_part(actor::text, '/'::text, 3) = ?", ^instance)
 +    )
    end
  
    defp restrict_instance(query, _), do: query
        name: data["name"],
        follower_address: data["followers"],
        following_address: data["following"],
-       bio: data["summary"],
+       bio: data["summary"] || "",
        actor_type: actor_type,
        also_known_as: Map.get(data, "alsoKnownAs", []),
        public_key: public_key,
  
    def fetch_follow_information_for_user(user) do
      with {:ok, following_data} <-
-            Fetcher.fetch_and_contain_remote_object_from_id(user.following_address),
+            Fetcher.fetch_and_contain_remote_object_from_id(user.following_address,
+              force_http: true
+            ),
           {:ok, hide_follows} <- collection_private(following_data),
           {:ok, followers_data} <-
-            Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address),
+            Fetcher.fetch_and_contain_remote_object_from_id(user.follower_address, force_http: true),
           {:ok, hide_followers} <- collection_private(followers_data) do
        {:ok,
         %{
      end
    end
  
-   def fetch_and_prepare_user_from_ap_id(ap_id) do
-     with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id),
+   def fetch_and_prepare_user_from_ap_id(ap_id, opts \\ []) do
+     with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id, opts),
           {:ok, data} <- user_data_from_user_object(data) do
        {:ok, maybe_update_follow_information(data)}
      else
    end
  
    def maybe_handle_clashing_nickname(data) do
-     nickname = data[:nickname]
-     with %User{} = old_user <- User.get_by_nickname(nickname),
+     with nickname when is_binary(nickname) <- data[:nickname],
+          %User{} = old_user <- User.get_by_nickname(nickname),
           {_, false} <- {:ap_id_comparison, data[:ap_id] == old_user.ap_id} do
        Logger.info(
          "Found an old user for #{nickname}, the old ap id is #{old_user.ap_id}, new one is #{
      else
        {:ap_id_comparison, true} ->
          Logger.info(
-           "Found an old user for #{nickname}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
+           "Found an old user for #{data[:nickname]}, but the ap id #{data[:ap_id]} is the same as the new user. Race condition? Not changing anything."
          )
  
        _ ->
      end
    end
  
-   def make_user_from_ap_id(ap_id) do
+   def make_user_from_ap_id(ap_id, opts \\ []) do
      user = User.get_cached_by_ap_id(ap_id)
  
      if user && !User.ap_enabled?(user) do
        Transmogrifier.upgrade_user_from_ap_id(ap_id)
      else
-       with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id) do
+       with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, opts) do
          if user do
            user
            |> User.remote_user_changeset(data)
index 7dccc00056c56cf42a67ba6aa62c049a5d67cec0,5272790d3149ad286ac10968ab263a59b6544403..701d69fd1576bbf7ecb11943e4d8529616f89044
@@@ -8,6 -8,7 +8,7 @@@ defmodule Pleroma.Web.MastodonAPI.Timel
    import Pleroma.Web.ControllerHelper,
      only: [add_link_headers: 2, add_link_headers: 3]
  
+   alias Pleroma.Config
    alias Pleroma.Pagination
    alias Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug
    alias Pleroma.Plugs.OAuthScopesPlug
    end
  
    defp restrict_unauthenticated?(true = _local_only) do
-     Pleroma.Config.get([:restrict_unauthenticated, :timelines, :local])
+     Config.restrict_unauthenticated_access?(:timelines, :local)
    end
  
    defp restrict_unauthenticated?(_) do
-     Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated])
+     Config.restrict_unauthenticated_access?(:timelines, :federated)
    end
  
    # GET /api/v1/timelines/public
          |> Map.put(:blocking_user, user)
          |> Map.put(:muting_user, user)
          |> Map.put(:reply_filtering_user, user)
 +        |> Map.put(:instance, params[:instance])
          |> ActivityPub.fetch_public_activities()
  
        conn
      with %Pleroma.List{title: _title, following: following} <- Pleroma.List.get(id, user) do
        params =
          params
-         |> Map.new(fn {key, value} -> {to_string(key), value} end)
-         |> Map.put("type", "Create")
-         |> Map.put("blocking_user", user)
-         |> Map.put("user", user)
-         |> Map.put("muting_user", user)
+         |> Map.put(:type, "Create")
+         |> Map.put(:blocking_user, user)
+         |> Map.put(:user, user)
+         |> Map.put(:muting_user, user)
  
        # we must filter the following list for the user to avoid leaking statuses the user
        # does not actually have permission to see (for more info, peruse security issue #270).
index aad7f61b50952bb0100a3dafa0b7cfa0b1cf072b,cba6b43d32a7d1a3e8519d5ebcc74d55e025256b..a0808c347171ae43e9a8b27bf575e336bd7ad30e
@@@ -155,11 -155,30 +155,30 @@@ defmodule Pleroma.Web.AdminAPI.AdminAPI
  
    describe "DELETE /api/pleroma/admin/users" do
      test "single user", %{admin: admin, conn: conn} do
-       user = insert(:user)
        clear_config([:instance, :federating], true)
  
+       user =
+         insert(:user,
+           avatar: %{"url" => [%{"href" => "https://someurl"}]},
+           banner: %{"url" => [%{"href" => "https://somebanner"}]},
+           bio: "Hello world!",
+           name: "A guy"
+         )
+       # Create some activities to check they got deleted later
+       follower = insert(:user)
+       {:ok, _} = CommonAPI.post(user, %{status: "test"})
+       {:ok, _, _, _} = CommonAPI.follow(user, follower)
+       {:ok, _, _, _} = CommonAPI.follow(follower, user)
+       user = Repo.get(User, user.id)
+       assert user.note_count == 1
+       assert user.follower_count == 1
+       assert user.following_count == 1
+       refute user.deactivated
        with_mock Pleroma.Web.Federator,
-         publish: fn _ -> nil end do
+         publish: fn _ -> nil end,
+         perform: fn _, _ -> nil end do
          conn =
            conn
            |> put_req_header("accept", "application/json")
  
          assert json_response(conn, 200) == [user.nickname]
  
+         user = Repo.get(User, user.id)
+         assert user.deactivated
+         assert user.avatar == %{}
+         assert user.banner == %{}
+         assert user.note_count == 0
+         assert user.follower_count == 0
+         assert user.following_count == 0
+         assert user.bio == ""
+         assert user.name == nil
          assert called(Pleroma.Web.Federator.publish(:_))
        end
      end
          "confirmation_pending" => false,
          "approval_pending" => false,
          "url" => user.ap_id,
-         "registration_reason" => nil
+         "registration_reason" => nil,
+         "actor_type" => "Person"
        }
  
        assert expected == json_response(conn, 200)
        user1: user1,
        user2: user2
      } do
-       assert json_response(conn, :no_content)
+       assert empty_json_response(conn)
        assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
        assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
  
      end
  
      test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
-       assert json_response(conn, :no_content)
+       assert empty_json_response(conn)
        assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
      end
    end
        user1: user1,
        user2: user2
      } do
-       assert json_response(conn, :no_content)
+       assert empty_json_response(conn)
        assert User.get_cached_by_id(user1.id).tags == []
        assert User.get_cached_by_id(user2.id).tags == ["y"]
  
      end
  
      test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
-       assert json_response(conn, :no_content)
+       assert empty_json_response(conn)
        assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
      end
    end
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => admin.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            },
            %{
              "deactivated" => user.deactivated,
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => user.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            },
            %{
              "deactivated" => user2.deactivated,
              "confirmation_pending" => false,
              "approval_pending" => true,
              "url" => user2.ap_id,
-             "registration_reason" => "I'm a chill dude"
+             "registration_reason" => "I'm a chill dude",
+             "actor_type" => "Person"
            }
          ]
          |> Enum.sort_by(& &1["nickname"])
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user2.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => user.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            },
            %{
              "deactivated" => admin.deactivated,
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => admin.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            },
            %{
              "deactivated" => false,
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => old_admin.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            }
          ]
          |> Enum.sort_by(& &1["nickname"])
              "confirmation_pending" => false,
              "approval_pending" => true,
              "url" => user.ap_id,
-             "registration_reason" => "Plz let me in!"
+             "registration_reason" => "Plz let me in!",
+             "actor_type" => "Person"
            }
          ]
          |> Enum.sort_by(& &1["nickname"])
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => admin.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            },
            %{
              "deactivated" => false,
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => second_admin.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            }
          ]
          |> Enum.sort_by(& &1["nickname"])
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => moderator.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => user1.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            },
            %{
              "deactivated" => false,
              "confirmation_pending" => false,
              "approval_pending" => false,
              "url" => user2.ap_id,
-             "registration_reason" => nil
+             "registration_reason" => nil,
+             "actor_type" => "Person"
            }
          ]
          |> Enum.sort_by(& &1["nickname"])
               }
      end
  
+     test "`active` filters out users pending approval", %{token: token} do
+       insert(:user, approval_pending: true)
+       %{id: user_id} = insert(:user, approval_pending: false)
+       %{id: admin_id} = token.user
+       conn =
+         build_conn()
+         |> assign(:user, token.user)
+         |> assign(:token, token)
+         |> get("/api/pleroma/admin/users?filters=active")
+       assert %{
+                "count" => 2,
+                "page_size" => 50,
+                "users" => [
+                  %{"id" => ^admin_id},
+                  %{"id" => ^user_id}
+                ]
+              } = json_response(conn, 200)
+     end
      test "it works with multiple filters" do
        admin = insert(:user, nickname: "john", is_admin: true)
        token = insert(:oauth_admin_token, user: admin)
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => user.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                     "confirmation_pending" => false,
                     "approval_pending" => false,
                     "url" => admin.ap_id,
-                    "registration_reason" => nil
+                    "registration_reason" => nil,
+                    "actor_type" => "Person"
                   }
                 ]
               }
                 "confirmation_pending" => false,
                 "approval_pending" => false,
                 "url" => user.ap_id,
-                "registration_reason" => nil
+                "registration_reason" => nil,
+                "actor_type" => "Person"
               }
  
      log_entry = Repo.one(ModerationLog)
      end
    end
  
+   describe "GET /api/pleroma/admin/users/:nickname/chats" do
+     setup do
+       user = insert(:user)
+       recipients = insert_list(3, :user)
+       Enum.each(recipients, fn recipient ->
+         CommonAPI.post_chat_message(user, recipient, "yo")
+       end)
+       %{user: user}
+     end
+     test "renders user's chats", %{conn: conn, user: user} do
+       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
+       assert json_response(conn, 200) |> length() == 3
+     end
+   end
+   describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do
+     setup do
+       user = insert(:user)
+       recipient = insert(:user)
+       CommonAPI.post_chat_message(user, recipient, "yo")
+       %{conn: conn} = oauth_access(["read:chats"])
+       %{conn: conn, user: user}
+     end
+     test "returns 403", %{conn: conn, user: user} do
+       conn
+       |> get("/api/pleroma/admin/users/#{user.nickname}/chats")
+       |> json_response(403)
+     end
+   end
+   describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do
+     setup do
+       user = insert(:user)
+       recipient = insert(:user)
+       CommonAPI.post_chat_message(user, recipient, "yo")
+       %{conn: build_conn(), user: user}
+     end
+     test "returns 403", %{conn: conn, user: user} do
+       conn
+       |> get("/api/pleroma/admin/users/#{user.nickname}/chats")
+       |> json_response(403)
+     end
+   end
    describe "GET /api/pleroma/admin/moderation_log" do
      setup do
        moderator = insert(:user, is_moderator: true)
        conn =
          patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
  
-       assert json_response(conn, 204) == ""
+       assert empty_json_response(conn) == ""
  
        ObanHelpers.perform_all()
  
  
    describe "instances" do
      test "GET /instances/:instance/statuses", %{conn: conn} do
 -      user = insert(:user, local: false, nickname: "archaeme@archae.me")
 -      user2 = insert(:user, local: false, nickname: "test@test.com")
 +      user = insert(:user, local: false, ap_id: "https://archae.me/users/archaeme")
 +      user2 = insert(:user, local: false, ap_id: "https://test.com/users/test")
        insert_pair(:note_activity, user: user)
        activity = insert(:note_activity, user: user2)
  
                 }"
  
        ObanHelpers.perform_all()
-       assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(first_user))
+       Pleroma.Emails.UserEmail.account_confirmation_email(first_user)
+       # temporary hackney fix until hackney max_connections bug is fixed
+       # https://git.pleroma.social/pleroma/pleroma/-/issues/2101
+       |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
+       |> assert_email_sent()
      end
    end
  
index 6acd512c78c30977b7f24d9567bcee974571cfdd,c6e0268fdbdaa80fd885a93976d27f704a98260e..8a70cfd64bdd02c67ae751b89e33bb492c82d9c1
@@@ -114,8 -114,16 +114,16 @@@ defmodule Pleroma.Web.MastodonAPI.Timel
        {:ok, _reply_from_friend} =
          CommonAPI.post(friend, %{status: "status", in_reply_to_status_id: reply_from_blockee})
  
-       res_conn = get(conn, "/api/v1/timelines/public")
-       [%{"id" => ^activity_id}] = json_response_and_validate_schema(res_conn, 200)
+       # Still shows replies from yourself
+       {:ok, %{id: reply_from_me}} =
+         CommonAPI.post(blocker, %{status: "status", in_reply_to_status_id: reply_from_blockee})
+       response =
+         get(conn, "/api/v1/timelines/public")
+         |> json_response_and_validate_schema(200)
+       assert length(response) == 2
+       [%{"id" => ^reply_from_me}, %{"id" => ^activity_id}] = response
      end
  
      test "doesn't return replies if follow is posting with users from blocked domain" do
        activities = json_response_and_validate_schema(res_conn, 200)
        [%{"id" => ^activity_id}] = activities
      end
 +
 +    test "can be filtered by instance", %{conn: conn} do
 +      user = insert(:user, ap_id: "https://lain.com/users/lain")
 +      insert(:note_activity, local: false)
 +      insert(:note_activity, local: false)
 +
 +      {:ok, _} = CommonAPI.post(user, %{status: "test"})
 +
 +      conn = get(conn, "/api/v1/timelines/public?instance=lain.com")
 +
 +      assert length(json_response_and_validate_schema(conn, :ok)) == 1
 +    end
    end
  
    defp local_and_remote_activities do
    describe "list" do
      setup do: oauth_access(["read:lists"])
  
+     test "does not contain retoots", %{user: user, conn: conn} do
+       other_user = insert(:user)
+       {:ok, activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
+       {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is stupid."})
+       {:ok, _} = CommonAPI.repeat(activity_one.id, other_user)
+       {:ok, list} = Pleroma.List.create("name", user)
+       {:ok, list} = Pleroma.List.follow(list, other_user)
+       conn = get(conn, "/api/v1/timelines/list/#{list.id}")
+       assert [%{"id" => id}] = json_response_and_validate_schema(conn, :ok)
+       assert id == to_string(activity_two.id)
+     end
+     test "works with pagination", %{user: user, conn: conn} do
+       other_user = insert(:user)
+       {:ok, list} = Pleroma.List.create("name", user)
+       {:ok, list} = Pleroma.List.follow(list, other_user)
+       Enum.each(1..30, fn i ->
+         CommonAPI.post(other_user, %{status: "post number #{i}"})
+       end)
+       res =
+         get(conn, "/api/v1/timelines/list/#{list.id}?limit=1")
+         |> json_response_and_validate_schema(:ok)
+       assert length(res) == 1
+       [first] = res
+       res =
+         get(conn, "/api/v1/timelines/list/#{list.id}?max_id=#{first["id"]}&limit=30")
+         |> json_response_and_validate_schema(:ok)
+       assert length(res) == 29
+     end
      test "list timeline", %{user: user, conn: conn} do
        other_user = insert(:user)
        {:ok, _activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."})
        assert length(json_response(res_conn, 200)) == 2
      end
  
+     test "with default settings on private instances, returns 403 for unauthenticated users", %{
+       conn: conn,
+       base_uri: base_uri,
+       error_response: error_response
+     } do
+       clear_config([:instance, :public], false)
+       clear_config([:restrict_unauthenticated, :timelines])
+       for local <- [true, false] do
+         res_conn = get(conn, "#{base_uri}?local=#{local}")
+         assert json_response(res_conn, :unauthorized) == error_response
+       end
+       ensure_authenticated_access(base_uri)
+     end
      test "with `%{local: true, federated: true}`, returns 403 for unauthenticated users", %{
        conn: conn,
        base_uri: base_uri,