diff options
| author | rinpatch <rinpatch@sdf.org> | 2019-04-17 12:22:32 +0300 | 
|---|---|---|
| committer | rinpatch <rinpatch@sdf.org> | 2019-04-17 12:22:32 +0300 | 
| commit | 627e5a0a4992cc19fc65a7e93a09c470c8e2bf33 (patch) | |
| tree | 0f38b475e8554863a1cbbd7750c19d4cd1336eb1 /test/web/activity_pub | |
| parent | d6ab701a14f7c9fb4d59953648c425e04725fc62 (diff) | |
| parent | 73df3046e014ae16e03f16a9c82921652cefcb54 (diff) | |
| download | pleroma-627e5a0a4992cc19fc65a7e93a09c470c8e2bf33.tar.gz pleroma-627e5a0a4992cc19fc65a7e93a09c470c8e2bf33.zip | |
Merge branch 'develop' into feature/database-compaction
Diffstat (limited to 'test/web/activity_pub')
| -rw-r--r-- | test/web/activity_pub/activity_pub_controller_test.exs | 402 | ||||
| -rw-r--r-- | test/web/activity_pub/activity_pub_test.exs | 601 | ||||
| -rw-r--r-- | test/web/activity_pub/mrf/anti_followbot_policy_test.exs | 72 | ||||
| -rw-r--r-- | test/web/activity_pub/mrf/hellthread_policy_test.exs | 73 | ||||
| -rw-r--r-- | test/web/activity_pub/mrf/keyword_policy_test.exs | 219 | ||||
| -rw-r--r-- | test/web/activity_pub/relay_test.exs | 4 | ||||
| -rw-r--r-- | test/web/activity_pub/transmogrifier_test.exs | 365 | ||||
| -rw-r--r-- | test/web/activity_pub/utils_test.exs | 208 | ||||
| -rw-r--r-- | test/web/activity_pub/views/object_view_test.exs | 2 | ||||
| -rw-r--r-- | test/web/activity_pub/views/user_view_test.exs | 62 | ||||
| -rw-r--r-- | test/web/activity_pub/visibilty_test.exs | 98 | 
11 files changed, 2050 insertions, 56 deletions
| diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 1c24b348c..7b1c60f15 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -1,9 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory -  alias Pleroma.Web.ActivityPub.{UserView, ObjectView} -  alias Pleroma.{Repo, User}    alias Pleroma.Activity +  alias Pleroma.Instances +  alias Pleroma.Object +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ObjectView +  alias Pleroma.Web.ActivityPub.UserView + +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end    describe "/relay" do      test "with the relay active, it returns the relay user", %{conn: conn} do @@ -18,17 +30,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do      test "with the relay disabled, it returns 404", %{conn: conn} do        Pleroma.Config.put([:instance, :allow_relay], false) -      res = -        conn -        |> get(activity_pub_path(conn, :relay)) -        |> json_response(404) +      conn +      |> get(activity_pub_path(conn, :relay)) +      |> json_response(404) +      |> assert        Pleroma.Config.put([:instance, :allow_relay], true)      end    end    describe "/users/:nickname" do -    test "it returns a json representation of the user", %{conn: conn} do +    test "it returns a json representation of the user with accept application/json", %{ +      conn: conn +    } do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/users/#{user.nickname}") + +      user = User.get_by_id(user.id) + +      assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) +    end + +    test "it returns a json representation of the user with accept application/activity+json", %{ +      conn: conn +    } do        user = insert(:user)        conn = @@ -36,14 +65,47 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do          |> put_req_header("accept", "application/activity+json")          |> get("/users/#{user.nickname}") -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id) + +      assert json_response(conn, 200) == UserView.render("user.json", %{user: user}) +    end + +    test "it returns a json representation of the user with accept application/ld+json", %{ +      conn: conn +    } do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header( +          "accept", +          "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" +        ) +        |> get("/users/#{user.nickname}") + +      user = User.get_by_id(user.id)        assert json_response(conn, 200) == UserView.render("user.json", %{user: user})      end    end    describe "/object/:uuid" do -    test "it returns a json representation of the object", %{conn: conn} do +    test "it returns a json representation of the object with accept application/json", %{ +      conn: conn +    } do +      note = insert(:note) +      uuid = String.split(note.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/objects/#{uuid}") + +      assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note}) +    end + +    test "it returns a json representation of the object with accept application/activity+json", +         %{conn: conn} do        note = insert(:note)        uuid = String.split(note.data["id"], "/") |> List.last() @@ -55,6 +117,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note})      end +    test "it returns a json representation of the object with accept application/ld+json", %{ +      conn: conn +    } do +      note = insert(:note) +      uuid = String.split(note.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header( +          "accept", +          "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" +        ) +        |> get("/objects/#{uuid}") + +      assert json_response(conn, 200) == ObjectView.render("object.json", %{object: note}) +    end +      test "it returns 404 for non-public messages", %{conn: conn} do        note = insert(:direct_note)        uuid = String.split(note.data["id"], "/") |> List.last() @@ -66,6 +145,59 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert json_response(conn, 404)      end + +    test "it returns 404 for tombstone objects", %{conn: conn} do +      tombstone = insert(:tombstone) +      uuid = String.split(tombstone.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}") + +      assert json_response(conn, 404) +    end +  end + +  describe "/object/:uuid/likes" do +    test "it returns the like activities in a collection", %{conn: conn} do +      like = insert(:like_activity) +      uuid = String.split(like.data["object"], "/") |> List.last() + +      result = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/objects/#{uuid}/likes") +        |> json_response(200) + +      assert List.first(result["first"]["orderedItems"])["id"] == like.data["id"] +    end +  end + +  describe "/activities/:uuid" do +    test "it returns a json representation of the activity", %{conn: conn} do +      activity = insert(:note_activity) +      uuid = String.split(activity.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/activities/#{uuid}") + +      assert json_response(conn, 200) == ObjectView.render("object.json", %{object: activity}) +    end + +    test "it returns 404 for non-public activities", %{conn: conn} do +      activity = insert(:direct_note_activity) +      uuid = String.split(activity.data["id"], "/") |> List.last() + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/activities/#{uuid}") + +      assert json_response(conn, 404) +    end    end    describe "/inbox" do @@ -82,6 +214,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        :timer.sleep(500)        assert Activity.get_by_ap_id(data["id"])      end + +    test "it clears `unreachable` federation status of the sender", %{conn: conn} do +      data = File.read!("test/fixtures/mastodon-post-activity.json") |> Poison.decode!() + +      sender_url = data["actor"] +      Instances.set_consistently_unreachable(sender_url) +      refute Instances.reachable?(sender_url) + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/inbox", data) + +      assert "ok" == json_response(conn, 200) +      assert Instances.reachable?(sender_url) +    end    end    describe "/users/:nickname/inbox" do @@ -103,9 +252,99 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        :timer.sleep(500)        assert Activity.get_by_ap_id(data["id"])      end + +    test "it accepts messages from actors that are followed by the user", %{conn: conn} do +      recipient = insert(:user) +      actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"}) + +      {:ok, recipient} = User.follow(recipient, actor) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() + +      object = +        data["object"] +        |> Map.put("attributedTo", actor.ap_id) + +      data = +        data +        |> Map.put("actor", actor.ap_id) +        |> Map.put("object", object) + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{recipient.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      :timer.sleep(500) +      assert Activity.get_by_ap_id(data["id"]) +    end + +    test "it rejects reads from other users", %{conn: conn} do +      user = insert(:user) +      otheruser = insert(:user) + +      conn = +        conn +        |> assign(:user, otheruser) +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}/inbox") + +      assert json_response(conn, 403) +    end + +    test "it returns a note activity in a collection", %{conn: conn} do +      note_activity = insert(:direct_note_activity) +      user = User.get_cached_by_ap_id(hd(note_activity.data["to"])) + +      conn = +        conn +        |> assign(:user, user) +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}/inbox") + +      assert response(conn, 200) =~ note_activity.data["object"]["content"] +    end + +    test "it clears `unreachable` federation status of the sender", %{conn: conn} do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("bcc", [user.ap_id]) + +      sender_host = URI.parse(data["actor"]).host +      Instances.set_consistently_unreachable(sender_host) +      refute Instances.reachable?(sender_host) + +      conn = +        conn +        |> assign(:valid_signature, true) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/inbox", data) + +      assert "ok" == json_response(conn, 200) +      assert Instances.reachable?(sender_host) +    end    end    describe "/users/:nickname/outbox" do +    test "it will not bomb when there is no activity", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get("/users/#{user.nickname}/outbox") + +      result = json_response(conn, 200) +      assert user.ap_id <> "/outbox" == result["id"] +    end +      test "it returns a note activity in a collection", %{conn: conn} do        note_activity = insert(:note_activity)        user = User.get_cached_by_ap_id(note_activity.data["actor"]) @@ -129,6 +368,121 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert response(conn, 200) =~ announce_activity.data["object"]      end + +    test "it rejects posts from other users", %{conn: conn} do +      data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() +      user = insert(:user) +      otheruser = insert(:user) + +      conn = +        conn +        |> assign(:user, otheruser) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/outbox", data) + +      assert json_response(conn, 403) +    end + +    test "it inserts an incoming create activity into the database", %{conn: conn} do +      data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() +      user = insert(:user) + +      conn = +        conn +        |> assign(:user, user) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/outbox", data) + +      result = json_response(conn, 201) +      assert Activity.get_by_ap_id(result["id"]) +    end + +    test "it rejects an incoming activity with bogus type", %{conn: conn} do +      data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() +      user = insert(:user) + +      data = +        data +        |> Map.put("type", "BadType") + +      conn = +        conn +        |> assign(:user, user) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/outbox", data) + +      assert json_response(conn, 400) +    end + +    test "it erects a tombstone when receiving a delete activity", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      data = %{ +        type: "Delete", +        object: %{ +          id: note_activity.data["object"]["id"] +        } +      } + +      conn = +        conn +        |> assign(:user, user) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/outbox", data) + +      result = json_response(conn, 201) +      assert Activity.get_by_ap_id(result["id"]) + +      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      assert object +      assert object.data["type"] == "Tombstone" +    end + +    test "it rejects delete activity of object from other actor", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = insert(:user) + +      data = %{ +        type: "Delete", +        object: %{ +          id: note_activity.data["object"]["id"] +        } +      } + +      conn = +        conn +        |> assign(:user, user) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/outbox", data) + +      assert json_response(conn, 400) +    end + +    test "it increases like count when receiving a like action", %{conn: conn} do +      note_activity = insert(:note_activity) +      user = User.get_cached_by_ap_id(note_activity.data["actor"]) + +      data = %{ +        type: "Like", +        object: %{ +          id: note_activity.data["object"]["id"] +        } +      } + +      conn = +        conn +        |> assign(:user, user) +        |> put_req_header("content-type", "application/activity+json") +        |> post("/users/#{user.nickname}/outbox", data) + +      result = json_response(conn, 201) +      assert Activity.get_by_ap_id(result["id"]) + +      object = Object.get_by_ap_id(note_activity.data["object"]["id"]) +      assert object +      assert object.data["like_count"] == 1 +    end    end    describe "/users/:nickname/followers" do @@ -145,6 +499,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["first"]["orderedItems"] == [user.ap_id]      end +    test "it returns returns empty if the user has 'hide_followers' set", %{conn: conn} do +      user = insert(:user) +      user_two = insert(:user, %{info: %{hide_followers: true}}) +      User.follow(user, user_two) + +      result = +        conn +        |> get("/users/#{user_two.nickname}/followers") +        |> json_response(200) + +      assert result["first"]["orderedItems"] == [] +      assert result["totalItems"] == 0 +    end +      test "it works for more than 10 users", %{conn: conn} do        user = insert(:user) @@ -186,11 +554,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        assert result["first"]["orderedItems"] == [user_two.ap_id]      end +    test "it returns returns empty if the user has 'hide_follows' set", %{conn: conn} do +      user = insert(:user, %{info: %{hide_follows: true}}) +      user_two = insert(:user) +      User.follow(user, user_two) + +      result = +        conn +        |> get("/users/#{user.nickname}/following") +        |> json_response(200) + +      assert result["first"]["orderedItems"] == [] +      assert result["totalItems"] == 0 +    end +      test "it works for more than 10 users", %{conn: conn} do        user = insert(:user)        Enum.each(1..15, fn _ -> -        user = Repo.get(User, user.id) +        user = User.get_by_id(user.id)          other_user = insert(:user)          User.follow(user, other_user)        end) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index bc9fcc75d..68bfb3858 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1,12 +1,70 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.ActivityPub.ActivityPubTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Builders.ActivityBuilder +  alias Pleroma.Instances +  alias Pleroma.Object +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI -  alias Pleroma.{Activity, Object, User} -  alias Pleroma.Builders.ActivityBuilder    import Pleroma.Factory +  import Tesla.Mock +  import Mock + +  setup do +    mock(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  describe "fetching restricted by visibility" do +    test "it restricts by the appropriate visibility" do +      user = insert(:user) + +      {:ok, public_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "public"}) + +      {:ok, direct_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "direct"}) + +      {:ok, unlisted_activity} = +        CommonAPI.post(user, %{"status" => ".", "visibility" => "unlisted"}) + +      {:ok, private_activity} = +        CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + +      activities = +        ActivityPub.fetch_activities([], %{:visibility => "direct", "actor_id" => user.ap_id}) + +      assert activities == [direct_activity] + +      activities = +        ActivityPub.fetch_activities([], %{:visibility => "unlisted", "actor_id" => user.ap_id}) + +      assert activities == [unlisted_activity] + +      activities = +        ActivityPub.fetch_activities([], %{:visibility => "private", "actor_id" => user.ap_id}) + +      assert activities == [private_activity] + +      activities = +        ActivityPub.fetch_activities([], %{:visibility => "public", "actor_id" => user.ap_id}) + +      assert activities == [public_activity] + +      activities = +        ActivityPub.fetch_activities([], %{ +          :visibility => ~w[private public], +          "actor_id" => user.ap_id +        }) + +      assert activities == [public_activity, private_activity] +    end +  end    describe "building a user from his ap id" do      test "it returns a user" do @@ -18,14 +76,71 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert user.info.ap_enabled        assert user.follower_address == "http://mastodon.example.org/users/admin/followers"      end + +    test "it fetches the appropriate tag-restricted posts" do +      user = insert(:user) + +      {:ok, status_one} = CommonAPI.post(user, %{"status" => ". #test"}) +      {:ok, status_two} = CommonAPI.post(user, %{"status" => ". #essais"}) +      {:ok, status_three} = CommonAPI.post(user, %{"status" => ". #test #reject"}) + +      fetch_one = ActivityPub.fetch_activities([], %{"tag" => "test"}) +      fetch_two = ActivityPub.fetch_activities([], %{"tag" => ["test", "essais"]}) + +      fetch_three = +        ActivityPub.fetch_activities([], %{ +          "tag" => ["test", "essais"], +          "tag_reject" => ["reject"] +        }) + +      fetch_four = +        ActivityPub.fetch_activities([], %{ +          "tag" => ["test"], +          "tag_all" => ["test", "reject"] +        }) + +      assert fetch_one == [status_one, status_three] +      assert fetch_two == [status_one, status_two, status_three] +      assert fetch_three == [status_one, status_two] +      assert fetch_four == [status_three] +    end    end    describe "insertion" do +    test "drops activities beyond a certain limit" do +      limit = Pleroma.Config.get([:instance, :remote_limit]) + +      random_text = +        :crypto.strong_rand_bytes(limit + 1) +        |> Base.encode64() +        |> binary_part(0, limit + 1) + +      data = %{ +        "ok" => true, +        "object" => %{ +          "content" => random_text +        } +      } + +      assert {:error, {:remote_limit_error, _}} = ActivityPub.insert(data) +    end + +    test "doesn't drop activities with content being null" do +      data = %{ +        "ok" => true, +        "object" => %{ +          "content" => nil +        } +      } + +      assert {:ok, _} = ActivityPub.insert(data) +    end +      test "returns the activity if one with the same id is already in" do        activity = insert(:note_activity)        {:ok, new_activity} = ActivityPub.insert(activity.data) -      assert activity == new_activity +      assert activity.id == new_activity.id      end      test "inserts a given map into the activity database, giving it an id if it has none." do @@ -102,7 +217,59 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert activity.data["to"] == ["user1", "user2"]        assert activity.actor == user.ap_id -      assert activity.recipients == ["user1", "user2"] +      assert activity.recipients == ["user1", "user2", user.ap_id] +    end + +    test "increases user note count only for public activities" do +      user = insert(:user) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "1", "visibility" => "public"}) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "unlisted"}) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "2", "visibility" => "private"}) + +      {:ok, _} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "3", "visibility" => "direct"}) + +      user = User.get_by_id(user.id) +      assert user.info.note_count == 2 +    end + +    test "increases replies count" do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) +      ap_id = activity.data["id"] +      reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} + +      # public +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 1 +      assert object.data["repliesCount"] == 1 + +      # unlisted +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      # private +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      # direct +      {:ok, _} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2      end    end @@ -142,7 +309,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      booster = insert(:user)      {:ok, user} = User.block(user, %{ap_id: activity_one.data["actor"]}) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => user}) +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      assert Enum.member?(activities, activity_three) @@ -150,7 +318,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      {:ok, user} = User.unblock(user, %{ap_id: activity_one.data["actor"]}) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => user}) +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      assert Enum.member?(activities, activity_three) @@ -158,17 +327,76 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      {:ok, user} = User.block(user, %{ap_id: activity_three.data["actor"]})      {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) -    %Activity{} = boost_activity = Activity.get_create_activity_by_object_ap_id(id) -    activity_three = Repo.get(Activity, activity_three.id) +    %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) +    activity_three = Activity.get_by_id(activity_three.id) + +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => user, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    refute Enum.member?(activities, activity_three) +    refute Enum.member?(activities, boost_activity) +    assert Enum.member?(activities, activity_one) + +    activities = +      ActivityPub.fetch_activities([], %{"blocking_user" => nil, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    assert Enum.member?(activities, boost_activity) +    assert Enum.member?(activities, activity_one) +  end + +  test "doesn't return muted activities" do +    activity_one = insert(:note_activity) +    activity_two = insert(:note_activity) +    activity_three = insert(:note_activity) +    user = insert(:user) +    booster = insert(:user) +    {:ok, user} = User.mute(user, %User{ap_id: activity_one.data["actor"]}) + +    activities = +      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    refute Enum.member?(activities, activity_one) + +    # Calling with 'with_muted' will deliver muted activities, too. +    activities = +      ActivityPub.fetch_activities([], %{ +        "muting_user" => user, +        "with_muted" => true, +        "skip_preload" => true +      }) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    assert Enum.member?(activities, activity_one) + +    {:ok, user} = User.unmute(user, %User{ap_id: activity_one.data["actor"]}) + +    activities = +      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true}) + +    assert Enum.member?(activities, activity_two) +    assert Enum.member?(activities, activity_three) +    assert Enum.member?(activities, activity_one) + +    {:ok, user} = User.mute(user, %User{ap_id: activity_three.data["actor"]}) +    {:ok, _announce, %{data: %{"id" => id}}} = CommonAPI.repeat(activity_three.id, booster) +    %Activity{} = boost_activity = Activity.get_create_by_object_ap_id(id) +    activity_three = Activity.get_by_id(activity_three.id) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => user}) +    activities = +      ActivityPub.fetch_activities([], %{"muting_user" => user, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      refute Enum.member?(activities, activity_three)      refute Enum.member?(activities, boost_activity)      assert Enum.member?(activities, activity_one) -    activities = ActivityPub.fetch_activities([], %{"blocking_user" => nil}) +    activities = ActivityPub.fetch_activities([], %{"muting_user" => nil, "skip_preload" => true})      assert Enum.member?(activities, activity_two)      assert Enum.member?(activities, activity_three) @@ -176,11 +404,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      assert Enum.member?(activities, activity_one)    end +  test "does include announces on request" do +    activity_three = insert(:note_activity) +    user = insert(:user) +    booster = insert(:user) + +    {:ok, user} = User.follow(user, booster) + +    {:ok, announce, _object} = CommonAPI.repeat(activity_three.id, booster) + +    [announce_activity] = ActivityPub.fetch_activities([user.ap_id | user.following]) + +    assert announce_activity.id == announce.id +  end + +  test "excludes reblogs on request" do +    user = insert(:user) +    {:ok, expected_activity} = ActivityBuilder.insert(%{"type" => "Create"}, %{:user => user}) +    {:ok, _} = ActivityBuilder.insert(%{"type" => "Announce"}, %{:user => user}) + +    [activity] = ActivityPub.fetch_user_activities(user, nil, %{"exclude_reblogs" => "true"}) + +    assert activity == expected_activity +  end +    describe "public fetch activities" do      test "doesn't retrieve unlisted activities" do        user = insert(:user) -      {:ok, unlisted_activity} = +      {:ok, _unlisted_activity} =          CommonAPI.post(user, %{"status" => "yeah", "visibility" => "unlisted"})        {:ok, listed_activity} = CommonAPI.post(user, %{"status" => "yeah"}) @@ -237,6 +489,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert length(activities) == 20        assert last == last_expected      end + +    test "doesn't return reblogs for users for whom reblogs have been muted" do +      activity = insert(:note_activity) +      user = insert(:user) +      booster = insert(:user) +      {:ok, user} = CommonAPI.hide_reblogs(user, booster) + +      {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + +      activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) + +      refute Enum.any?(activities, fn %{id: id} -> id == activity.id end) +    end + +    test "returns reblogs for users for whom reblogs have not been muted" do +      activity = insert(:note_activity) +      user = insert(:user) +      booster = insert(:user) +      {:ok, user} = CommonAPI.hide_reblogs(user, booster) +      {:ok, user} = CommonAPI.show_reblogs(user, booster) + +      {:ok, activity, _} = CommonAPI.repeat(activity.id, booster) + +      activities = ActivityPub.fetch_activities([], %{"muting_user" => user}) + +      assert Enum.any?(activities, fn %{id: id} -> id == activity.id end) +    end    end    describe "like an object" do @@ -262,7 +541,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert like_activity == same_like_activity        assert object.data["likes"] == [user.ap_id] -      [note_activity] = Activity.all_by_object_ap_id(object.data["id"]) +      [note_activity] = Activity.get_all_create_by_object_ap_id(object.data["id"])        assert note_activity.data["object"]["like_count"] == 1        {:ok, _like_activity, object} = ActivityPub.like(user_two, object) @@ -286,7 +565,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        {:ok, _, _, object} = ActivityPub.unlike(user, object)        assert object.data["like_count"] == 0 -      assert Repo.get(Activity, like_activity.id) == nil +      assert Activity.get_by_id(like_activity.id) == nil      end    end @@ -337,7 +616,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert unannounce_activity.data["actor"] == user.ap_id        assert unannounce_activity.data["context"] == announce_activity.data["context"] -      assert Repo.get(Activity, announce_activity.id) == nil +      assert Activity.get_by_id(announce_activity.id) == nil      end    end @@ -372,6 +651,43 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end    end +  describe "fetching an object" do +    test "it fetches an object" do +      {:ok, object} = +        ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") + +      assert activity = Activity.get_create_by_object_ap_id(object.data["id"]) +      assert activity.data["id"] + +      {:ok, object_again} = +        ActivityPub.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") + +      assert [attachment] = object.data["attachment"] +      assert is_list(attachment["url"]) + +      assert object == object_again +    end + +    test "it works with objects only available via Ostatus" do +      {:ok, object} = ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873") +      assert activity = Activity.get_create_by_object_ap_id(object.data["id"]) +      assert activity.data["id"] + +      {:ok, object_again} = +        ActivityPub.fetch_object_from_id("https://shitposter.club/notice/2827873") + +      assert object == object_again +    end + +    test "it correctly stitches up conversations between ostatus and ap" do +      last = "https://mstdn.io/users/mayuutann/statuses/99568293732299394" +      {:ok, object} = ActivityPub.fetch_object_from_id(last) + +      object = Object.get_by_ap_id(object.data["inReplyTo"]) +      assert object +    end +  end +    describe "following / unfollowing" do      test "creates a follow activity" do        follower = insert(:user) @@ -439,9 +755,88 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do        assert delete.data["actor"] == note.data["actor"]        assert delete.data["object"] == note.data["object"]["id"] -      assert Repo.get(Activity, delete.id) != nil +      assert Activity.get_by_id(delete.id) != nil -      assert Repo.get(Object, object.id) == nil +      assert Repo.get(Object, object.id).data["type"] == "Tombstone" +    end + +    test "decrements user note count only for public activities" do +      user = insert(:user, info: %{note_count: 10}) + +      {:ok, a1} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "public"}) + +      {:ok, a2} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "unlisted"}) + +      {:ok, a3} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "private"}) + +      {:ok, a4} = +        CommonAPI.post(User.get_by_id(user.id), %{"status" => "yeah", "visibility" => "direct"}) + +      {:ok, _} = a1.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() +      {:ok, _} = a2.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() +      {:ok, _} = a3.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() +      {:ok, _} = a4.data["object"]["id"] |> Object.get_by_ap_id() |> ActivityPub.delete() + +      user = User.get_by_id(user.id) +      assert user.info.note_count == 10 +    end + +    test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do +      user = insert(:user) +      note = insert(:note_activity) + +      {:ok, object} = +        Object.get_by_ap_id(note.data["object"]["id"]) +        |> Object.change(%{ +          data: %{ +            "actor" => note.data["object"]["actor"], +            "id" => note.data["object"]["id"], +            "to" => [user.ap_id], +            "type" => "Note" +          } +        }) +        |> Object.update_and_set_cache() + +      {:ok, delete} = ActivityPub.delete(object) + +      assert user.ap_id in delete.data["to"] +    end + +    test "decreases reply count" do +      user = insert(:user) +      user2 = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"}) +      reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id} +      ap_id = activity.data["id"] + +      {:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public")) +      {:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted")) +      {:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private")) +      {:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct")) + +      _ = CommonAPI.delete(direct_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      _ = CommonAPI.delete(private_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 2 +      assert object.data["repliesCount"] == 2 + +      _ = CommonAPI.delete(public_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 1 +      assert object.data["repliesCount"] == 1 + +      _ = CommonAPI.delete(unlisted_reply.id, user2) +      assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id) +      assert data["object"]["repliesCount"] == 0 +      assert object.data["repliesCount"] == 0      end    end @@ -479,10 +874,9 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do            "in_reply_to_status_id" => private_activity_2.id          }) -      assert user1.following == [user3.ap_id <> "/followers", user1.ap_id] -        activities = ActivityPub.fetch_activities([user1.ap_id | user1.following]) +      private_activity_1 = Activity.get_by_ap_id_with_object(private_activity_1.data["id"])        assert [public_activity, private_activity_1, private_activity_3] == activities        assert length(activities) == 3 @@ -514,6 +908,177 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end    end +  test "it can fetch peertube videos" do +    {:ok, object} = +      ActivityPub.fetch_object_from_id( +        "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" +      ) + +    assert object +  end + +  test "returned pinned statuses" do +    Pleroma.Config.put([:instance, :max_pinned_statuses], 3) +    user = insert(:user) + +    {:ok, activity_one} = CommonAPI.post(user, %{"status" => "HI!!!"}) +    {:ok, activity_two} = CommonAPI.post(user, %{"status" => "HI!!!"}) +    {:ok, activity_three} = CommonAPI.post(user, %{"status" => "HI!!!"}) + +    CommonAPI.pin(activity_one.id, user) +    user = refresh_record(user) + +    CommonAPI.pin(activity_two.id, user) +    user = refresh_record(user) + +    CommonAPI.pin(activity_three.id, user) +    user = refresh_record(user) + +    activities = ActivityPub.fetch_user_activities(user, nil, %{"pinned" => "true"}) + +    assert 3 = length(activities) +  end + +  test "it can create a Flag activity" do +    reporter = insert(:user) +    target_account = insert(:user) +    {:ok, activity} = CommonAPI.post(target_account, %{"status" => "foobar"}) +    context = Utils.generate_context_id() +    content = "foobar" + +    reporter_ap_id = reporter.ap_id +    target_ap_id = target_account.ap_id +    activity_ap_id = activity.data["id"] + +    assert {:ok, activity} = +             ActivityPub.flag(%{ +               actor: reporter, +               context: context, +               account: target_account, +               statuses: [activity], +               content: content +             }) + +    assert %Activity{ +             actor: ^reporter_ap_id, +             data: %{ +               "type" => "Flag", +               "content" => ^content, +               "context" => ^context, +               "object" => [^target_ap_id, ^activity_ap_id] +             } +           } = activity +  end + +  describe "publish_one/1" do +    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = +               ActivityPub.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: NaiveDateTime.utc_now() +               }) + +      assert called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_reachable` on successful federation if `unreachable_since` is nil", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = +               ActivityPub.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: nil +               }) + +      refute called(Instances.set_reachable(inbox)) +    end + +    test_with_mock "calls `Instances.set_unreachable` on target inbox on non-2xx HTTP response code", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://404.site/users/nick1/inbox" + +      assert {:error, _} = +               ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "it calls `Instances.set_unreachable` on target inbox on request error of any kind", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://connrefused.site/users/nick1/inbox" + +      assert {:error, _} = +               ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      assert called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_unreachable` if target is reachable", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://200.site/users/nick1/inbox" + +      assert {:ok, _} = ActivityPub.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) + +      refute called(Instances.set_unreachable(inbox)) +    end + +    test_with_mock "does NOT call `Instances.set_unreachable` if target instance has non-nil `unreachable_since`", +                   Instances, +                   [:passthrough], +                   [] do +      actor = insert(:user) +      inbox = "http://connrefused.site/users/nick1/inbox" + +      assert {:error, _} = +               ActivityPub.publish_one(%{ +                 inbox: inbox, +                 json: "{}", +                 actor: actor, +                 id: 1, +                 unreachable_since: NaiveDateTime.utc_now() +               }) + +      refute called(Instances.set_unreachable(inbox)) +    end +  end +    def data_uri do      File.read!("test/fixtures/avatar_data_uri")    end diff --git a/test/web/activity_pub/mrf/anti_followbot_policy_test.exs b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs new file mode 100644 index 000000000..37a7bfcf7 --- /dev/null +++ b/test/web/activity_pub/mrf/anti_followbot_policy_test.exs @@ -0,0 +1,72 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicyTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  alias Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy + +  describe "blocking based on attributes" do +    test "matches followbots by nickname" do +      actor = insert(:user, %{nickname: "followbot@example.com"}) +      target = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "type" => "Follow", +        "actor" => actor.ap_id, +        "object" => target.ap_id, +        "id" => "https://example.com/activities/1234" +      } + +      {:reject, nil} = AntiFollowbotPolicy.filter(message) +    end + +    test "matches followbots by display name" do +      actor = insert(:user, %{name: "Federation Bot"}) +      target = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "type" => "Follow", +        "actor" => actor.ap_id, +        "object" => target.ap_id, +        "id" => "https://example.com/activities/1234" +      } + +      {:reject, nil} = AntiFollowbotPolicy.filter(message) +    end +  end + +  test "it allows non-followbots" do +    actor = insert(:user) +    target = insert(:user) + +    message = %{ +      "@context" => "https://www.w3.org/ns/activitystreams", +      "type" => "Follow", +      "actor" => actor.ap_id, +      "object" => target.ap_id, +      "id" => "https://example.com/activities/1234" +    } + +    {:ok, _} = AntiFollowbotPolicy.filter(message) +  end + +  test "it gracefully handles nil display names" do +    actor = insert(:user, %{name: nil}) +    target = insert(:user) + +    message = %{ +      "@context" => "https://www.w3.org/ns/activitystreams", +      "type" => "Follow", +      "actor" => actor.ap_id, +      "object" => target.ap_id, +      "id" => "https://example.com/activities/1234" +    } + +    {:ok, _} = AntiFollowbotPolicy.filter(message) +  end +end diff --git a/test/web/activity_pub/mrf/hellthread_policy_test.exs b/test/web/activity_pub/mrf/hellthread_policy_test.exs new file mode 100644 index 000000000..eb6ee4d04 --- /dev/null +++ b/test/web/activity_pub/mrf/hellthread_policy_test.exs @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicyTest do +  use Pleroma.DataCase +  import Pleroma.Factory + +  import Pleroma.Web.ActivityPub.MRF.HellthreadPolicy + +  setup do +    user = insert(:user) + +    message = %{ +      "actor" => user.ap_id, +      "cc" => [user.follower_address], +      "type" => "Create", +      "to" => [ +        "https://www.w3.org/ns/activitystreams#Public", +        "https://instance.tld/users/user1", +        "https://instance.tld/users/user2", +        "https://instance.tld/users/user3" +      ] +    } + +    [user: user, message: message] +  end + +  describe "reject" do +    test "rejects the message if the recipient count is above reject_threshold", %{ +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 2}) + +      {:reject, nil} = filter(message) +    end + +    test "does not reject the message if the recipient count is below reject_threshold", %{ +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3}) + +      assert {:ok, ^message} = filter(message) +    end +  end + +  describe "delist" do +    test "delists the message if the recipient count is above delist_threshold", %{ +      user: user, +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 2, reject_threshold: 0}) + +      {:ok, message} = filter(message) +      assert user.follower_address in message["to"] +      assert "https://www.w3.org/ns/activitystreams#Public" in message["cc"] +    end + +    test "does not delist the message if the recipient count is below delist_threshold", %{ +      message: message +    } do +      Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 4, reject_threshold: 0}) + +      assert {:ok, ^message} = filter(message) +    end +  end + +  test "excludes follower collection and public URI from threshold count", %{message: message} do +    Pleroma.Config.put([:mrf_hellthread], %{delist_threshold: 0, reject_threshold: 3}) + +    assert {:ok, ^message} = filter(message) +  end +end diff --git a/test/web/activity_pub/mrf/keyword_policy_test.exs b/test/web/activity_pub/mrf/keyword_policy_test.exs new file mode 100644 index 000000000..602892a37 --- /dev/null +++ b/test/web/activity_pub/mrf/keyword_policy_test.exs @@ -0,0 +1,219 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicyTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.MRF.KeywordPolicy + +  setup do +    Pleroma.Config.put([:mrf_keyword], %{reject: [], federated_timeline_removal: [], replace: []}) +  end + +  describe "rejecting based on keywords" do +    test "rejects if string matches in content" do +      Pleroma.Config.put([:mrf_keyword, :reject], ["pun"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "content" => "just a daily reminder that compLAINer is a good pun", +          "summary" => "" +        } +      } + +      assert {:reject, nil} == KeywordPolicy.filter(message) +    end + +    test "rejects if string matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :reject], ["pun"]) + +      message = %{ +        "type" => "Create", +        "object" => %{ +          "summary" => "just a daily reminder that compLAINer is a good pun", +          "content" => "" +        } +      } + +      assert {:reject, nil} == KeywordPolicy.filter(message) +    end + +    test "rejects if regex matches in content" do +      Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "object" => %{ +                     "content" => "just a daily reminder that #{content} is a good pun", +                     "summary" => "" +                   } +                 } + +                 {:reject, nil} == KeywordPolicy.filter(message) +               end) +    end + +    test "rejects if regex matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :reject], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "object" => %{ +                     "summary" => "just a daily reminder that #{content} is a good pun", +                     "content" => "" +                   } +                 } + +                 {:reject, nil} == KeywordPolicy.filter(message) +               end) +    end +  end + +  describe "delisting from ftl based on keywords" do +    test "delists if string matches in content" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"]) + +      message = %{ +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "content" => "just a daily reminder that compLAINer is a good pun", +          "summary" => "" +        } +      } + +      {:ok, result} = KeywordPolicy.filter(message) +      assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] +      refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"] +    end + +    test "delists if string matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], ["pun"]) + +      message = %{ +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "type" => "Create", +        "object" => %{ +          "summary" => "just a daily reminder that compLAINer is a good pun", +          "content" => "" +        } +      } + +      {:ok, result} = KeywordPolicy.filter(message) +      assert ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] +      refute ["https://www.w3.org/ns/activitystreams#Public"] == result["to"] +    end + +    test "delists if regex matches in content" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{ +                     "content" => "just a daily reminder that #{content} is a good pun", +                     "summary" => "" +                   } +                 } + +                 {:ok, result} = KeywordPolicy.filter(message) + +                 ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and +                   not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"]) +               end) +    end + +    test "delists if regex matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :federated_timeline_removal], [~r/comp[lL][aA][iI][nN]er/]) + +      assert true == +               Enum.all?(["complainer", "compLainer", "compLAiNer", "compLAINer"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{ +                     "summary" => "just a daily reminder that #{content} is a good pun", +                     "content" => "" +                   } +                 } + +                 {:ok, result} = KeywordPolicy.filter(message) + +                 ["https://www.w3.org/ns/activitystreams#Public"] == result["cc"] and +                   not (["https://www.w3.org/ns/activitystreams#Public"] == result["to"]) +               end) +    end +  end + +  describe "replacing keywords" do +    test "replaces keyword if string matches in content" do +      Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}]) + +      message = %{ +        "type" => "Create", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "object" => %{"content" => "ZFS is opensource", "summary" => ""} +      } + +      {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message) +      assert result == "ZFS is free software" +    end + +    test "replaces keyword if string matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :replace], [{"opensource", "free software"}]) + +      message = %{ +        "type" => "Create", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "object" => %{"summary" => "ZFS is opensource", "content" => ""} +      } + +      {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message) +      assert result == "ZFS is free software" +    end + +    test "replaces keyword if regex matches in content" do +      Pleroma.Config.put([:mrf_keyword, :replace], [ +        {~r/open(-|\s)?source\s?(software)?/, "free software"} +      ]) + +      assert true == +               Enum.all?(["opensource", "open-source", "open source"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{"content" => "ZFS is #{content}", "summary" => ""} +                 } + +                 {:ok, %{"object" => %{"content" => result}}} = KeywordPolicy.filter(message) +                 result == "ZFS is free software" +               end) +    end + +    test "replaces keyword if regex matches in summary" do +      Pleroma.Config.put([:mrf_keyword, :replace], [ +        {~r/open(-|\s)?source\s?(software)?/, "free software"} +      ]) + +      assert true == +               Enum.all?(["opensource", "open-source", "open source"], fn content -> +                 message = %{ +                   "type" => "Create", +                   "to" => ["https://www.w3.org/ns/activitystreams#Public"], +                   "object" => %{"summary" => "ZFS is #{content}", "content" => ""} +                 } + +                 {:ok, %{"object" => %{"summary" => result}}} = KeywordPolicy.filter(message) +                 result == "ZFS is free software" +               end) +    end +  end +end diff --git a/test/web/activity_pub/relay_test.exs b/test/web/activity_pub/relay_test.exs index 41d13e055..21a63c493 100644 --- a/test/web/activity_pub/relay_test.exs +++ b/test/web/activity_pub/relay_test.exs @@ -1,3 +1,7 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.ActivityPub.RelayTest do    use Pleroma.DataCase diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index ea9d9fe58..5559cdf87 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -1,17 +1,27 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only +  defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do    use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.ActivityPub.Utils -  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.OStatus -  alias Pleroma.{Activity, Object} -  alias Pleroma.User -  alias Pleroma.Repo    alias Pleroma.Web.Websub.WebsubClientSubscription    import Pleroma.Factory    alias Pleroma.Web.CommonAPI +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end +    describe "handle_incoming" do      test "it ignores an incoming notice if we already have it" do        activity = insert(:note_activity) @@ -43,13 +53,11 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        returned_object = Object.normalize(returned_activity.data["object"])        assert activity = -               Activity.get_create_activity_by_object_ap_id( +               Activity.get_create_by_object_ap_id(                   "tag:shitposter.club,2017-05-05:noticeId=2827873:objectType=comment"                 )        assert returned_object.data["inReplyToAtomUri"] == "https://shitposter.club/notice/2827873" - -      assert returned_object.data["inReplyToStatusId"] == activity.id      end      test "it works for incoming notices" do @@ -160,6 +168,36 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert object.data["url"] == "https://prismo.news/posts/83"      end +    test "it cleans up incoming notices which are not really DMs" do +      user = insert(:user) +      other_user = insert(:user) + +      to = [user.ap_id, other_user.ap_id] + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("to", to) +        |> Map.put("cc", []) + +      object = +        data["object"] +        |> Map.put("to", to) +        |> Map.put("cc", []) + +      data = Map.put(data, "object", object) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["to"] == [] +      assert data["cc"] == to + +      object = data["object"] + +      assert object["to"] == [] +      assert object["cc"] == to +    end +      test "it works for incoming follow requests" do        user = insert(:user) @@ -261,7 +299,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert data["object"] ==                 "http://mastodon.example.org/users/admin/statuses/99541947525187367" -      assert Activity.get_create_activity_by_object_ap_id(data["object"]) +      assert Activity.get_create_by_object_ap_id(data["object"])      end      test "it works for incoming announces with an existing activity" do @@ -283,7 +321,70 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert data["object"] == activity.data["object"] -      assert Activity.get_create_activity_by_object_ap_id(data["object"]).id == activity.id +      assert Activity.get_create_by_object_ap_id(data["object"]).id == activity.id +    end + +    test "it does not clobber the addressing on announce activities" do +      user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) + +      data = +        File.read!("test/fixtures/mastodon-announce.json") +        |> Poison.decode!() +        |> Map.put("object", activity.data["object"]["id"]) +        |> Map.put("to", ["http://mastodon.example.org/users/admin/followers"]) +        |> Map.put("cc", []) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["to"] == ["http://mastodon.example.org/users/admin/followers"] +    end + +    test "it ensures that as:Public activities make it to their followers collection" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("actor", user.ap_id) +        |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"]) +        |> Map.put("cc", []) + +      object = +        data["object"] +        |> Map.put("attributedTo", user.ap_id) +        |> Map.put("to", ["https://www.w3.org/ns/activitystreams#Public"]) +        |> Map.put("cc", []) + +      data = Map.put(data, "object", object) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert data["cc"] == [User.ap_followers(user)] +    end + +    test "it ensures that address fields become lists" do +      user = insert(:user) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Poison.decode!() +        |> Map.put("actor", user.ap_id) +        |> Map.put("to", nil) +        |> Map.put("cc", nil) + +      object = +        data["object"] +        |> Map.put("attributedTo", user.ap_id) +        |> Map.put("to", nil) +        |> Map.put("cc", nil) + +      data = Map.put(data, "object", object) + +      {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) + +      assert !is_nil(data["to"]) +      assert !is_nil(data["cc"])      end      test "it works for incoming update activities" do @@ -365,7 +466,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{local: false}} = Transmogrifier.handle_incoming(data) -      refute Repo.get(Activity, activity.id) +      refute Activity.get_by_id(activity.id)      end      test "it fails for incoming deletes with spoofed origin" do @@ -385,7 +486,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(data) -      assert Repo.get(Activity, activity.id) +      assert Activity.get_by_id(activity.id)      end      test "it works for incoming unannounces with an existing notice" do @@ -543,7 +644,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert activity.data["object"] == follow_activity.data["id"] -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == true      end @@ -565,7 +666,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(accept_data)        assert activity.data["object"] == follow_activity.data["id"] -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == true      end @@ -585,7 +686,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(accept_data)        assert activity.data["object"] == follow_activity.data["id"] -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == true      end @@ -604,7 +705,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(accept_data) -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        refute User.following?(follower, followed) == true      end @@ -623,7 +724,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(accept_data) -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        refute User.following?(follower, followed) == true      end @@ -648,7 +749,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, activity} = Transmogrifier.handle_incoming(reject_data)        refute activity.local -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == false      end @@ -670,7 +771,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, %Activity{data: _}} = Transmogrifier.handle_incoming(reject_data) -      follower = Repo.get(User, follower.id) +      follower = User.get_by_id(follower.id)        assert User.following?(follower, followed) == false      end @@ -686,6 +787,60 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(data)      end + +    test "it remaps video URLs as attachments if necessary" do +      {:ok, object} = +        ActivityPub.fetch_object_from_id( +          "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" +        ) + +      attachment = %{ +        "type" => "Link", +        "mediaType" => "video/mp4", +        "href" => +          "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", +        "mimeType" => "video/mp4", +        "size" => 5_015_880, +        "url" => [ +          %{ +            "href" => +              "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", +            "mediaType" => "video/mp4", +            "type" => "Link" +          } +        ], +        "width" => 480 +      } + +      assert object.data["url"] == +               "https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3" + +      assert object.data["attachment"] == [attachment] +    end + +    test "it accepts Flag activities" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "test post"}) +      object = Object.normalize(activity.data["object"]) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "cc" => [user.ap_id], +        "object" => [user.ap_id, object.data["id"]], +        "type" => "Flag", +        "content" => "blocked AND reported!!!", +        "actor" => other_user.ap_id +      } + +      assert {:ok, activity} = Transmogrifier.handle_incoming(message) + +      assert activity.data["object"] == [user.ap_id, object.data["id"]] +      assert activity.data["content"] == "blocked AND reported!!!" +      assert activity.data["actor"] == other_user.ap_id +      assert activity.data["cc"] == [user.ap_id] +    end    end    describe "prepare outgoing" do @@ -797,12 +952,61 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert length(modified["object"]["tag"]) == 2        assert is_nil(modified["object"]["emoji"]) -      assert is_nil(modified["object"]["likes"])        assert is_nil(modified["object"]["like_count"])        assert is_nil(modified["object"]["announcements"])        assert is_nil(modified["object"]["announcement_count"])        assert is_nil(modified["object"]["context_id"])      end + +    test "it strips internal fields of article" do +      activity = insert(:article_activity) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + +      assert length(modified["object"]["tag"]) == 2 + +      assert is_nil(modified["object"]["emoji"]) +      assert is_nil(modified["object"]["like_count"]) +      assert is_nil(modified["object"]["announcements"]) +      assert is_nil(modified["object"]["announcement_count"]) +      assert is_nil(modified["object"]["context_id"]) +    end + +    test "it adds like collection to object" do +      activity = insert(:note_activity) +      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + +      assert modified["object"]["likes"]["type"] == "OrderedCollection" +      assert modified["object"]["likes"]["totalItems"] == 0 +    end + +    test "the directMessage flag is present" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu :moominmamma:"}) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + +      assert modified["directMessage"] == false + +      {:ok, activity} = +        CommonAPI.post(user, %{"status" => "@#{other_user.nickname} :moominmamma:"}) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + +      assert modified["directMessage"] == false + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} :moominmamma:", +          "visibility" => "direct" +        }) + +      {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) + +      assert modified["directMessage"] == true +    end    end    describe "user upgrade" do @@ -821,7 +1025,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        {:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})        assert "http://localhost:4001/users/rye@niu.moe/followers" in activity.recipients -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id)        assert user.info.note_count == 1        {:ok, user} = Transmogrifier.upgrade_user_from_ap_id("https://niu.moe/users/rye") @@ -829,13 +1033,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.info.note_count == 1        assert user.follower_address == "https://niu.moe/users/rye/followers" -      # Wait for the background task -      :timer.sleep(1000) - -      user = Repo.get(User, user.id) +      user = User.get_by_id(user.id)        assert user.info.note_count == 1 -      activity = Repo.get(Activity, activity.id) +      activity = Activity.get_by_id(activity.id)        assert user.follower_address in activity.recipients        assert %{ @@ -858,10 +1059,10 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        refute "..." in activity.recipients -      unrelated_activity = Repo.get(Activity, unrelated_activity.id) +      unrelated_activity = Activity.get_by_id(unrelated_activity.id)        refute user.follower_address in unrelated_activity.recipients -      user_two = Repo.get(User, user_two.id) +      user_two = User.get_by_id(user_two.id)        assert user.follower_address in user_two.following        refute "..." in user_two.following      end @@ -933,4 +1134,114 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        :error = Transmogrifier.handle_incoming(data)      end    end + +  describe "general origin containment" do +    test "contain_origin_from_id() catches obvious spoofing attempts" do +      data = %{ +        "id" => "http://example.com/~alyssa/activities/1234.json" +      } + +      :error = +        Transmogrifier.contain_origin_from_id( +          "http://example.org/~alyssa/activities/1234.json", +          data +        ) +    end + +    test "contain_origin_from_id() allows alternate IDs within the same origin domain" do +      data = %{ +        "id" => "http://example.com/~alyssa/activities/1234.json" +      } + +      :ok = +        Transmogrifier.contain_origin_from_id( +          "http://example.com/~alyssa/activities/1234", +          data +        ) +    end + +    test "contain_origin_from_id() allows matching IDs" do +      data = %{ +        "id" => "http://example.com/~alyssa/activities/1234.json" +      } + +      :ok = +        Transmogrifier.contain_origin_from_id( +          "http://example.com/~alyssa/activities/1234.json", +          data +        ) +    end + +    test "users cannot be collided through fake direction spoofing attempts" do +      insert(:user, %{ +        nickname: "rye@niu.moe", +        local: false, +        ap_id: "https://niu.moe/users/rye", +        follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"}) +      }) + +      {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye") +    end + +    test "all objects with fake directions are rejected by the object fetcher" do +      {:error, _} = +        ActivityPub.fetch_and_contain_remote_object_from_id( +          "https://info.pleroma.site/activity4.json" +        ) +    end +  end + +  describe "reserialization" do +    test "successfully reserializes a message with inReplyTo == nil" do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => [], +        "type" => "Create", +        "object" => %{ +          "to" => ["https://www.w3.org/ns/activitystreams#Public"], +          "cc" => [], +          "type" => "Note", +          "content" => "Hi", +          "inReplyTo" => nil, +          "attributedTo" => user.ap_id +        }, +        "actor" => user.ap_id +      } + +      {:ok, activity} = Transmogrifier.handle_incoming(message) + +      {:ok, _} = Transmogrifier.prepare_outgoing(activity.data) +    end + +    test "successfully reserializes a message with AS2 objects in IR" do +      user = insert(:user) + +      message = %{ +        "@context" => "https://www.w3.org/ns/activitystreams", +        "to" => ["https://www.w3.org/ns/activitystreams#Public"], +        "cc" => [], +        "type" => "Create", +        "object" => %{ +          "to" => ["https://www.w3.org/ns/activitystreams#Public"], +          "cc" => [], +          "type" => "Note", +          "content" => "Hi", +          "inReplyTo" => nil, +          "attributedTo" => user.ap_id, +          "tag" => [ +            %{"name" => "#2hu", "href" => "http://example.com/2hu", "type" => "Hashtag"}, +            %{"name" => "Bob", "href" => "http://example.com/bob", "type" => "Mention"} +          ] +        }, +        "actor" => user.ap_id +      } + +      {:ok, activity} = Transmogrifier.handle_incoming(message) + +      {:ok, _} = Transmogrifier.prepare_outgoing(activity.data) +    end +  end  end diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs new file mode 100644 index 000000000..758214e68 --- /dev/null +++ b/test/web/activity_pub/utils_test.exs @@ -0,0 +1,208 @@ +defmodule Pleroma.Web.ActivityPub.UtilsTest do +  use Pleroma.DataCase +  alias Pleroma.Activity +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Utils +  alias Pleroma.Web.CommonAPI + +  import Pleroma.Factory + +  describe "fetch the latest Follow" do +    test "fetches the latest Follow activity" do +      %Activity{data: %{"type" => "Follow"}} = activity = insert(:follow_activity) +      follower = Repo.get_by(User, ap_id: activity.data["actor"]) +      followed = Repo.get_by(User, ap_id: activity.data["object"]) + +      assert activity == Utils.fetch_latest_follow(follower, followed) +    end +  end + +  describe "fetch the latest Block" do +    test "fetches the latest Block activity" do +      blocker = insert(:user) +      blocked = insert(:user) +      {:ok, activity} = ActivityPub.block(blocker, blocked) + +      assert activity == Utils.fetch_latest_block(blocker, blocked) +    end +  end + +  describe "determine_explicit_mentions()" do +    test "works with an object that has mentions" do +      object = %{ +        "tag" => [ +          %{ +            "type" => "Mention", +            "href" => "https://example.com/~alyssa", +            "name" => "Alyssa P. Hacker" +          } +        ] +      } + +      assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"] +    end + +    test "works with an object that does not have mentions" do +      object = %{ +        "tag" => [ +          %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"} +        ] +      } + +      assert Utils.determine_explicit_mentions(object) == [] +    end + +    test "works with an object that has mentions and other tags" do +      object = %{ +        "tag" => [ +          %{ +            "type" => "Mention", +            "href" => "https://example.com/~alyssa", +            "name" => "Alyssa P. Hacker" +          }, +          %{"type" => "Hashtag", "href" => "https://example.com/tag/2hu", "name" => "2hu"} +        ] +      } + +      assert Utils.determine_explicit_mentions(object) == ["https://example.com/~alyssa"] +    end + +    test "works with an object that has no tags" do +      object = %{} + +      assert Utils.determine_explicit_mentions(object) == [] +    end + +    test "works with an object that has only IR tags" do +      object = %{"tag" => ["2hu"]} + +      assert Utils.determine_explicit_mentions(object) == [] +    end +  end + +  describe "make_like_data" do +    setup do +      user = insert(:user) +      other_user = insert(:user) +      third_user = insert(:user) +      [user: user, other_user: other_user, third_user: third_user] +    end + +    test "addresses actor's follower address if the activity is public", %{ +      user: user, +      other_user: other_user, +      third_user: third_user +    } do +      expected_to = Enum.sort([user.ap_id, other_user.follower_address]) +      expected_cc = Enum.sort(["https://www.w3.org/ns/activitystreams#Public", third_user.ap_id]) + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => +            "hey @#{other_user.nickname}, @#{third_user.nickname} how about beering together this weekend?" +        }) + +      %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil) +      assert Enum.sort(to) == expected_to +      assert Enum.sort(cc) == expected_cc +    end + +    test "does not adress actor's follower address if the activity is not public", %{ +      user: user, +      other_user: other_user, +      third_user: third_user +    } do +      expected_to = Enum.sort([user.ap_id]) +      expected_cc = [third_user.ap_id] + +      {:ok, activity} = +        CommonAPI.post(user, %{ +          "status" => "@#{other_user.nickname} @#{third_user.nickname} bought a new swimsuit!", +          "visibility" => "private" +        }) + +      %{"to" => to, "cc" => cc} = Utils.make_like_data(other_user, activity, nil) +      assert Enum.sort(to) == expected_to +      assert Enum.sort(cc) == expected_cc +    end +  end + +  describe "fetch_ordered_collection" do +    import Tesla.Mock + +    test "fetches the first OrderedCollectionPage when an OrderedCollection is encountered" do +      mock(fn +        %{method: :get, url: "http://mastodon.com/outbox"} -> +          json(%{"type" => "OrderedCollection", "first" => "http://mastodon.com/outbox?page=true"}) + +        %{method: :get, url: "http://mastodon.com/outbox?page=true"} -> +          json(%{"type" => "OrderedCollectionPage", "orderedItems" => ["ok"]}) +      end) + +      assert Utils.fetch_ordered_collection("http://mastodon.com/outbox", 1) == ["ok"] +    end + +    test "fetches several pages in the right order one after another, but only the specified amount" do +      mock(fn +        %{method: :get, url: "http://example.com/outbox"} -> +          json(%{ +            "type" => "OrderedCollectionPage", +            "orderedItems" => [0], +            "next" => "http://example.com/outbox?page=1" +          }) + +        %{method: :get, url: "http://example.com/outbox?page=1"} -> +          json(%{ +            "type" => "OrderedCollectionPage", +            "orderedItems" => [1], +            "next" => "http://example.com/outbox?page=2" +          }) + +        %{method: :get, url: "http://example.com/outbox?page=2"} -> +          json(%{"type" => "OrderedCollectionPage", "orderedItems" => [2]}) +      end) + +      assert Utils.fetch_ordered_collection("http://example.com/outbox", 0) == [0] +      assert Utils.fetch_ordered_collection("http://example.com/outbox", 1) == [0, 1] +    end + +    test "returns an error if the url doesn't have an OrderedCollection/Page" do +      mock(fn +        %{method: :get, url: "http://example.com/not-an-outbox"} -> +          json(%{"type" => "NotAnOutbox"}) +      end) + +      assert {:error, _} = Utils.fetch_ordered_collection("http://example.com/not-an-outbox", 1) +    end + +    test "returns the what was collected if there are less pages than specified" do +      mock(fn +        %{method: :get, url: "http://example.com/outbox"} -> +          json(%{ +            "type" => "OrderedCollectionPage", +            "orderedItems" => [0], +            "next" => "http://example.com/outbox?page=1" +          }) + +        %{method: :get, url: "http://example.com/outbox?page=1"} -> +          json(%{"type" => "OrderedCollectionPage", "orderedItems" => [1]}) +      end) + +      assert Utils.fetch_ordered_collection("http://example.com/outbox", 5) == [0, 1] +    end +  end + +  test "make_json_ld_header/0" do +    assert Utils.make_json_ld_header() == %{ +             "@context" => [ +               "https://www.w3.org/ns/activitystreams", +               "http://localhost:4001/schemas/litepub-0.1.jsonld", +               %{ +                 "@language" => "und" +               } +             ] +           } +  end +end diff --git a/test/web/activity_pub/views/object_view_test.exs b/test/web/activity_pub/views/object_view_test.exs index d144a77fc..d939fc5a7 100644 --- a/test/web/activity_pub/views/object_view_test.exs +++ b/test/web/activity_pub/views/object_view_test.exs @@ -2,8 +2,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectViewTest do    use Pleroma.DataCase    import Pleroma.Factory -  alias Pleroma.Web.CommonAPI    alias Pleroma.Web.ActivityPub.ObjectView +  alias Pleroma.Web.CommonAPI    test "renders a note object" do      note = insert(:note) diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs index 7fc870e96..9fb9455d2 100644 --- a/test/web/activity_pub/views/user_view_test.exs +++ b/test/web/activity_pub/views/user_view_test.exs @@ -15,4 +15,66 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do      assert String.contains?(result["publicKey"]["publicKeyPem"], "BEGIN PUBLIC KEY")    end + +  test "Does not add an avatar image if the user hasn't set one" do +    user = insert(:user) +    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +    result = UserView.render("user.json", %{user: user}) +    refute result["icon"] +    refute result["image"] + +    user = +      insert(:user, +        avatar: %{"url" => [%{"href" => "https://someurl"}]}, +        info: %{ +          banner: %{"url" => [%{"href" => "https://somebanner"}]} +        } +      ) + +    {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +    result = UserView.render("user.json", %{user: user}) +    assert result["icon"]["url"] == "https://someurl" +    assert result["image"]["url"] == "https://somebanner" +  end + +  describe "endpoints" do +    test "local users have a usable endpoints structure" do +      user = insert(:user) +      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +      result = UserView.render("user.json", %{user: user}) + +      assert result["id"] == user.ap_id + +      %{ +        "sharedInbox" => _, +        "oauthAuthorizationEndpoint" => _, +        "oauthRegistrationEndpoint" => _, +        "oauthTokenEndpoint" => _ +      } = result["endpoints"] +    end + +    test "remote users have an empty endpoints structure" do +      user = insert(:user, local: false) +      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +      result = UserView.render("user.json", %{user: user}) + +      assert result["id"] == user.ap_id +      assert result["endpoints"] == %{} +    end + +    test "instance users do not expose oAuth endpoints" do +      user = insert(:user, nickname: nil, local: true) +      {:ok, user} = Pleroma.Web.WebFinger.ensure_keys_present(user) + +      result = UserView.render("user.json", %{user: user}) + +      refute result["endpoints"]["oauthAuthorizationEndpoint"] +      refute result["endpoints"]["oauthRegistrationEndpoint"] +      refute result["endpoints"]["oauthTokenEndpoint"] +    end +  end  end diff --git a/test/web/activity_pub/visibilty_test.exs b/test/web/activity_pub/visibilty_test.exs new file mode 100644 index 000000000..24b96c4aa --- /dev/null +++ b/test/web/activity_pub/visibilty_test.exs @@ -0,0 +1,98 @@ +defmodule Pleroma.Web.ActivityPub.VisibilityTest do +  use Pleroma.DataCase + +  alias Pleroma.Web.ActivityPub.Visibility +  alias Pleroma.Web.CommonAPI +  import Pleroma.Factory + +  setup do +    user = insert(:user) +    mentioned = insert(:user) +    following = insert(:user) +    unrelated = insert(:user) +    {:ok, following} = Pleroma.User.follow(following, user) + +    {:ok, public} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "public"}) + +    {:ok, private} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "private"}) + +    {:ok, direct} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "direct"}) + +    {:ok, unlisted} = +      CommonAPI.post(user, %{"status" => "@#{mentioned.nickname}", "visibility" => "unlisted"}) + +    %{ +      public: public, +      private: private, +      direct: direct, +      unlisted: unlisted, +      user: user, +      mentioned: mentioned, +      following: following, +      unrelated: unrelated +    } +  end + +  test "is_direct?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +    assert Visibility.is_direct?(direct) +    refute Visibility.is_direct?(public) +    refute Visibility.is_direct?(private) +    refute Visibility.is_direct?(unlisted) +  end + +  test "is_public?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +    refute Visibility.is_public?(direct) +    assert Visibility.is_public?(public) +    refute Visibility.is_public?(private) +    assert Visibility.is_public?(unlisted) +  end + +  test "is_private?", %{public: public, private: private, direct: direct, unlisted: unlisted} do +    refute Visibility.is_private?(direct) +    refute Visibility.is_private?(public) +    assert Visibility.is_private?(private) +    refute Visibility.is_private?(unlisted) +  end + +  test "visible_for_user?", %{ +    public: public, +    private: private, +    direct: direct, +    unlisted: unlisted, +    user: user, +    mentioned: mentioned, +    following: following, +    unrelated: unrelated +  } do +    # All visible to author + +    assert Visibility.visible_for_user?(public, user) +    assert Visibility.visible_for_user?(private, user) +    assert Visibility.visible_for_user?(unlisted, user) +    assert Visibility.visible_for_user?(direct, user) + +    # All visible to a mentioned user + +    assert Visibility.visible_for_user?(public, mentioned) +    assert Visibility.visible_for_user?(private, mentioned) +    assert Visibility.visible_for_user?(unlisted, mentioned) +    assert Visibility.visible_for_user?(direct, mentioned) + +    # DM not visible for just follower + +    assert Visibility.visible_for_user?(public, following) +    assert Visibility.visible_for_user?(private, following) +    assert Visibility.visible_for_user?(unlisted, following) +    refute Visibility.visible_for_user?(direct, following) + +    # Public and unlisted visible for unrelated user + +    assert Visibility.visible_for_user?(public, unrelated) +    assert Visibility.visible_for_user?(unlisted, unrelated) +    refute Visibility.visible_for_user?(private, unrelated) +    refute Visibility.visible_for_user?(direct, unrelated) +  end +end | 
