diff options
Diffstat (limited to 'test/web')
30 files changed, 1775 insertions, 798 deletions
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index a8f1f0e26..5c8d20ac4 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -820,21 +820,29 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do        activity: activity      } do        user = insert(:user) +      conn = assign(conn, :user, user)        object = Map.put(activity["object"], "sensitive", true)        activity = Map.put(activity, "object", object) -      result = +      response =          conn -        |> assign(:user, user)          |> put_req_header("content-type", "application/activity+json")          |> post("/users/#{user.nickname}/outbox", activity)          |> json_response(201) -      assert Activity.get_by_ap_id(result["id"]) -      assert result["object"] -      assert %Object{data: object} = Object.normalize(result["object"]) -      assert object["sensitive"] == activity["object"]["sensitive"] -      assert object["content"] == activity["object"]["content"] +      assert Activity.get_by_ap_id(response["id"]) +      assert response["object"] +      assert %Object{data: response_object} = Object.normalize(response["object"]) +      assert response_object["sensitive"] == true +      assert response_object["content"] == activity["object"]["content"] + +      representation = +        conn +        |> put_req_header("accept", "application/activity+json") +        |> get(response["id"]) +        |> json_response(200) + +      assert representation["object"]["sensitive"] == true      end      test "it rejects an incoming activity with bogus type", %{conn: conn, activity: activity} do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 1ac4f9896..4b70af5a6 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1267,143 +1267,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      end    end -  describe "deletion" do -    setup do: clear_config([:instance, :rewrite_policy]) - -    test "it reverts deletion on error" do -      note = insert(:note_activity) -      object = Object.normalize(note) - -      with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do -        assert {:error, :reverted} = ActivityPub.delete(object) -      end - -      assert Repo.aggregate(Activity, :count, :id) == 1 -      assert Repo.get(Object, object.id) == object -      assert Activity.get_by_id(note.id) == note -    end - -    test "it creates a delete activity and deletes the original object" do -      note = insert(:note_activity) -      object = Object.normalize(note) -      {:ok, delete} = ActivityPub.delete(object) - -      assert delete.data["type"] == "Delete" -      assert delete.data["actor"] == note.data["actor"] -      assert delete.data["object"] == object.data["id"] - -      assert Activity.get_by_id(delete.id) != nil - -      assert Repo.get(Object, object.id).data["type"] == "Tombstone" -    end - -    test "it doesn't fail when an activity was already deleted" do -      {:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete() - -      assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete() -    end - -    test "decrements user note count only for public activities" do -      user = insert(:user, note_count: 10) - -      {:ok, a1} = -        CommonAPI.post(User.get_cached_by_id(user.id), %{ -          "status" => "yeah", -          "visibility" => "public" -        }) - -      {:ok, a2} = -        CommonAPI.post(User.get_cached_by_id(user.id), %{ -          "status" => "yeah", -          "visibility" => "unlisted" -        }) - -      {:ok, a3} = -        CommonAPI.post(User.get_cached_by_id(user.id), %{ -          "status" => "yeah", -          "visibility" => "private" -        }) - -      {:ok, a4} = -        CommonAPI.post(User.get_cached_by_id(user.id), %{ -          "status" => "yeah", -          "visibility" => "direct" -        }) - -      {:ok, _} = Object.normalize(a1) |> ActivityPub.delete() -      {:ok, _} = Object.normalize(a2) |> ActivityPub.delete() -      {:ok, _} = Object.normalize(a3) |> ActivityPub.delete() -      {:ok, _} = Object.normalize(a4) |> ActivityPub.delete() - -      user = User.get_cached_by_id(user.id) -      assert user.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) -      object = Object.normalize(note) - -      {:ok, object} = -        object -        |> Object.change(%{ -          data: %{ -            "actor" => object.data["actor"], -            "id" => object.data["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 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 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 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 object.data["repliesCount"] == 0 -    end - -    test "it passes delete activity through MRF before deleting the object" do -      Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy) - -      note = insert(:note_activity) -      object = Object.normalize(note) - -      {:error, {:reject, _}} = ActivityPub.delete(object) - -      assert Activity.get_by_id(note.id) -      assert Repo.get(Object, object.id).data["type"] == object.data["type"] -    end -  end -    describe "timeline post-processing" do      test "it filters broken threads" do        user1 = insert(:user) diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index a7ad8e646..4cae52077 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -1,6 +1,7 @@  defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do    use Pleroma.DataCase +  alias Pleroma.Object    alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.ObjectValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator @@ -49,6 +50,98 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do      end    end +  describe "deletes" do +    setup do +      user = insert(:user) +      {:ok, post_activity} = CommonAPI.post(user, %{"status" => "cancel me daddy"}) + +      {:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"]) +      {:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id) + +      %{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete} +    end + +    test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do +      {:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, []) + +      assert valid_post_delete["deleted_activity_id"] +    end + +    test "it is invalid if the object isn't in a list of certain types", %{ +      valid_post_delete: valid_post_delete +    } do +      object = Object.get_by_ap_id(valid_post_delete["object"]) + +      data = +        object.data +        |> Map.put("type", "Like") + +      {:ok, _object} = +        object +        |> Ecto.Changeset.change(%{data: data}) +        |> Object.update_and_set_cache() + +      {:error, cng} = ObjectValidator.validate(valid_post_delete, []) +      assert {:object, {"object not in allowed types", []}} in cng.errors +    end + +    test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do +      assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, [])) +    end + +    test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do +      no_id = +        valid_post_delete +        |> Map.delete("id") + +      {:error, cng} = ObjectValidator.validate(no_id, []) + +      assert {:id, {"can't be blank", [validation: :required]}} in cng.errors +    end + +    test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do +      missing_object = +        valid_post_delete +        |> Map.put("object", "http://does.not/exist") + +      {:error, cng} = ObjectValidator.validate(missing_object, []) + +      assert {:object, {"can't find object", []}} in cng.errors +    end + +    test "it's invalid if the actor of the object and the actor of delete are from different domains", +         %{valid_post_delete: valid_post_delete} do +      valid_user = insert(:user) + +      valid_other_actor = +        valid_post_delete +        |> Map.put("actor", valid_user.ap_id) + +      assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, [])) + +      invalid_other_actor = +        valid_post_delete +        |> Map.put("actor", "https://gensokyo.2hu/users/raymoo") + +      {:error, cng} = ObjectValidator.validate(invalid_other_actor, []) + +      assert {:actor, {"is not allowed to delete object", []}} in cng.errors +    end + +    test "it's valid if the actor of the object is a local superuser", +         %{valid_post_delete: valid_post_delete} do +      user = +        insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo") + +      valid_other_actor = +        valid_post_delete +        |> Map.put("actor", user.ap_id) + +      {:ok, _, meta} = ObjectValidator.validate(valid_other_actor, []) +      assert meta[:do_not_federate] +    end +  end +    describe "likes" do      setup do        user = insert(:user) diff --git a/test/web/activity_pub/object_validators/types/recipients_test.exs b/test/web/activity_pub/object_validators/types/recipients_test.exs new file mode 100644 index 000000000..f278f039b --- /dev/null +++ b/test/web/activity_pub/object_validators/types/recipients_test.exs @@ -0,0 +1,27 @@ +defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do +  alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients +  use Pleroma.DataCase + +  test "it asserts that all elements of the list are object ids" do +    list = ["https://lain.com/users/lain", "invalid"] + +    assert :error == Recipients.cast(list) +  end + +  test "it works with a list" do +    list = ["https://lain.com/users/lain"] +    assert {:ok, list} == Recipients.cast(list) +  end + +  test "it works with a list with whole objects" do +    list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}] +    resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"] +    assert {:ok, resulting_list} == Recipients.cast(list) +  end + +  test "it turns a single string into a list" do +    recipient = "https://lain.com/users/lain" + +    assert {:ok, [recipient]} == Recipients.cast(recipient) +  end +end diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 9271d5ba1..404b129ea 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -3,17 +3,74 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.SideEffectsTest do +  use Oban.Testing, repo: Pleroma.Repo    use Pleroma.DataCase +  alias Pleroma.Activity    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.SideEffects    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  import Mock + +  describe "delete objects" do +    setup do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, op} = CommonAPI.post(other_user, %{"status" => "big oof"}) +      {:ok, post} = CommonAPI.post(user, %{"status" => "hey", "in_reply_to_id" => op}) +      object = Object.normalize(post) +      {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) +      {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) +      {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) +      {:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true) +      %{user: user, delete: delete, post: post, object: object, delete_user: delete_user, op: op} +    end + +    test "it handles object deletions", %{ +      delete: delete, +      post: post, +      object: object, +      user: user, +      op: op +    } do +      with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough], +        stream_out: fn _ -> nil end, +        stream_out_participations: fn _, _ -> nil end do +        {:ok, delete, _} = SideEffects.handle(delete) +        user = User.get_cached_by_ap_id(object.data["actor"]) + +        assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete)) +        assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user)) +      end + +      object = Object.get_by_id(object.id) +      assert object.data["type"] == "Tombstone" +      refute Activity.get_by_id(post.id) + +      user = User.get_by_id(user.id) +      assert user.note_count == 0 + +      object = Object.normalize(op.data["object"], false) + +      assert object.data["repliesCount"] == 0 +    end + +    test "it handles user deletions", %{delete_user: delete, user: user} do +      {:ok, _delete, _} = SideEffects.handle(delete) +      ObanHelpers.perform_all() + +      assert User.get_cached_by_ap_id(user.ap_id).deactivated +    end +  end    describe "EmojiReact objects" do      setup do diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs new file mode 100644 index 000000000..f235a8e63 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -0,0 +1,86 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Transmogrifier.DeleteHandlingTest do +  use Oban.Testing, repo: Pleroma.Repo +  use Pleroma.DataCase + +  alias Pleroma.Activity +  alias Pleroma.Object +  alias Pleroma.Tests.ObanHelpers +  alias Pleroma.User +  alias Pleroma.Web.ActivityPub.Transmogrifier + +  import Pleroma.Factory + +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) +    :ok +  end + +  test "it works for incoming deletes" do +    activity = insert(:note_activity) +    deleting_user = insert(:user) + +    data = +      File.read!("test/fixtures/mastodon-delete.json") +      |> Poison.decode!() +      |> Map.put("actor", deleting_user.ap_id) +      |> put_in(["object", "id"], activity.data["object"]) + +    {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = +      Transmogrifier.handle_incoming(data) + +    assert id == data["id"] + +    # We delete the Create activity because we base our timelines on it. +    # This should be changed after we unify objects and activities +    refute Activity.get_by_id(activity.id) +    assert actor == deleting_user.ap_id + +    # Objects are replaced by a tombstone object. +    object = Object.normalize(activity.data["object"]) +    assert object.data["type"] == "Tombstone" +  end + +  test "it fails for incoming deletes with spoofed origin" do +    activity = insert(:note_activity) +    %{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") + +    data = +      File.read!("test/fixtures/mastodon-delete.json") +      |> Poison.decode!() +      |> Map.put("actor", ap_id) +      |> put_in(["object", "id"], activity.data["object"]) + +    assert match?({:error, _}, Transmogrifier.handle_incoming(data)) +  end + +  @tag capture_log: true +  test "it works for incoming user deletes" do +    %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + +    data = +      File.read!("test/fixtures/mastodon-delete-user.json") +      |> Poison.decode!() + +    {:ok, _} = Transmogrifier.handle_incoming(data) +    ObanHelpers.perform_all() + +    assert User.get_cached_by_ap_id(ap_id).deactivated +  end + +  test "it fails for incoming user deletes with spoofed origin" do +    %{ap_id: ap_id} = insert(:user) + +    data = +      File.read!("test/fixtures/mastodon-delete-user.json") +      |> Poison.decode!() +      |> Map.put("actor", ap_id) + +    assert match?({:error, _}, Transmogrifier.handle_incoming(data)) + +    assert User.get_cached_by_ap_id(ap_id) +  end +end diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 7deac2909..336ddb323 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -729,84 +729,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.locked == true      end -    test "it works for incoming deletes" do -      activity = insert(:note_activity) -      deleting_user = insert(:user) - -      data = -        File.read!("test/fixtures/mastodon-delete.json") -        |> Poison.decode!() - -      object = -        data["object"] -        |> Map.put("id", activity.data["object"]) - -      data = -        data -        |> Map.put("object", object) -        |> Map.put("actor", deleting_user.ap_id) - -      {:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} = -        Transmogrifier.handle_incoming(data) - -      assert id == data["id"] -      refute Activity.get_by_id(activity.id) -      assert actor == deleting_user.ap_id -    end - -    test "it fails for incoming deletes with spoofed origin" do -      activity = insert(:note_activity) - -      data = -        File.read!("test/fixtures/mastodon-delete.json") -        |> Poison.decode!() - -      object = -        data["object"] -        |> Map.put("id", activity.data["object"]) - -      data = -        data -        |> Map.put("object", object) - -      assert capture_log(fn -> -               :error = Transmogrifier.handle_incoming(data) -             end) =~ -               "[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}" - -      assert Activity.get_by_id(activity.id) -    end - -    @tag capture_log: true -    test "it works for incoming user deletes" do -      %{ap_id: ap_id} = -        insert(:user, ap_id: "http://mastodon.example.org/users/admin", local: false) - -      data = -        File.read!("test/fixtures/mastodon-delete-user.json") -        |> Poison.decode!() - -      {:ok, _} = Transmogrifier.handle_incoming(data) -      ObanHelpers.perform_all() - -      refute User.get_cached_by_ap_id(ap_id) -    end - -    test "it fails for incoming user deletes with spoofed origin" do -      %{ap_id: ap_id} = insert(:user) - -      data = -        File.read!("test/fixtures/mastodon-delete-user.json") -        |> Poison.decode!() -        |> Map.put("actor", ap_id) - -      assert capture_log(fn -> -               assert :error == Transmogrifier.handle_incoming(data) -             end) =~ "Object containment failed" - -      assert User.get_cached_by_ap_id(ap_id) -    end -      test "it works for incoming unannounces with an existing notice" do        user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 1862a9589..4697af50e 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -6,19 +6,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    use Pleroma.Web.ConnCase    use Oban.Testing, repo: Pleroma.Repo -  import Pleroma.Factory    import ExUnit.CaptureLog +  import Mock +  import Pleroma.Factory    alias Pleroma.Activity    alias Pleroma.Config    alias Pleroma.ConfigDB    alias Pleroma.HTML +  alias Pleroma.MFA    alias Pleroma.ModerationLog    alias Pleroma.Repo    alias Pleroma.ReportNote    alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.UserInviteToken +  alias Pleroma.Web    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MediaProxy @@ -146,17 +149,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      test "single user", %{admin: admin, conn: conn} do        user = insert(:user) -      conn = -        conn -        |> put_req_header("accept", "application/json") -        |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") +      with_mock Pleroma.Web.Federator, +        publish: fn _ -> nil end do +        conn = +          conn +          |> put_req_header("accept", "application/json") +          |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") -      log_entry = Repo.one(ModerationLog) +        ObanHelpers.perform_all() -      assert ModerationLog.get_log_entry_message(log_entry) == -               "@#{admin.nickname} deleted users: @#{user.nickname}" +        assert User.get_by_nickname(user.nickname).deactivated + +        log_entry = Repo.one(ModerationLog) -      assert json_response(conn, 200) == user.nickname +        assert ModerationLog.get_log_entry_message(log_entry) == +                 "@#{admin.nickname} deleted users: @#{user.nickname}" + +        assert json_response(conn, 200) == [user.nickname] + +        assert called(Pleroma.Web.Federator.publish(:_)) +      end      end      test "multiple users", %{admin: admin, conn: conn} do @@ -737,6 +749,39 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do               }      end +    test "pagination works correctly with service users", %{conn: conn} do +      service1 = insert(:user, ap_id: Web.base_url() <> "/relay") +      service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch") +      insert_list(25, :user) + +      assert %{"count" => 26, "page_size" => 10, "users" => users1} = +               conn +               |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"}) +               |> json_response(200) + +      assert Enum.count(users1) == 10 +      assert service1 not in [users1] +      assert service2 not in [users1] + +      assert %{"count" => 26, "page_size" => 10, "users" => users2} = +               conn +               |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"}) +               |> json_response(200) + +      assert Enum.count(users2) == 10 +      assert service1 not in [users2] +      assert service2 not in [users2] + +      assert %{"count" => 26, "page_size" => 10, "users" => users3} = +               conn +               |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"}) +               |> json_response(200) + +      assert Enum.count(users3) == 6 +      assert service1 not in [users3] +      assert service2 not in [users3] +    end +      test "renders empty array for the second page", %{conn: conn} do        insert(:user) @@ -1234,6 +1279,38 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do               "@#{admin.nickname} deactivated users: @#{user.nickname}"    end +  describe "PUT disable_mfa" do +    test "returns 200 and disable 2fa", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true} +          } +        ) + +      response = +        conn +        |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname}) +        |> json_response(200) + +      assert response == user.nickname +      mfa_settings = refresh_record(user).multi_factor_authentication_settings + +      refute mfa_settings.enabled +      refute mfa_settings.totp.confirmed +    end + +    test "returns 404 if user not found", %{conn: conn} do +      response = +        conn +        |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"}) +        |> json_response(404) + +      assert response == "Not found" +    end +  end +    describe "POST /api/pleroma/admin/users/invite_token" do      test "without options", %{conn: conn} do        conn = post(conn, "/api/pleroma/admin/users/invite_token") @@ -1620,6 +1697,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end    end +  describe "GET /api/pleroma/admin/statuses/:id" do +    test "not found", %{conn: conn} do +      assert conn +             |> get("/api/pleroma/admin/statuses/not_found") +             |> json_response(:not_found) +    end + +    test "shows activity", %{conn: conn} do +      activity = insert(:note_activity) + +      response = +        conn +        |> get("/api/pleroma/admin/statuses/#{activity.id}") +        |> json_response(200) + +      assert response["id"] == activity.id +    end +  end +    describe "PUT /api/pleroma/admin/statuses/:id" do      setup do        activity = insert(:note_activity) @@ -3526,7 +3622,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "success", %{conn: conn} do -      base_url = Pleroma.Web.base_url() +      base_url = Web.base_url()        app_name = "Trusted app"        response = @@ -3547,7 +3643,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      end      test "with trusted", %{conn: conn} do -      base_url = Pleroma.Web.base_url() +      base_url = Web.base_url()        app_name = "Trusted app"        response = diff --git a/test/web/auth/pleroma_authenticator_test.exs b/test/web/auth/pleroma_authenticator_test.exs new file mode 100644 index 000000000..7125c5081 --- /dev/null +++ b/test/web/auth/pleroma_authenticator_test.exs @@ -0,0 +1,43 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.PleromaAuthenticatorTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.Web.Auth.PleromaAuthenticator +  import Pleroma.Factory + +  setup do +    password = "testpassword" +    name = "AgentSmith" +    user = insert(:user, nickname: name, password_hash: Comeonin.Pbkdf2.hashpwsalt(password)) +    {:ok, [user: user, name: name, password: password]} +  end + +  test "get_user/authorization", %{user: user, name: name, password: password} do +    params = %{"authorization" => %{"name" => name, "password" => password}} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:ok, user} == res +  end + +  test "get_user/authorization with invalid password", %{name: name} do +    params = %{"authorization" => %{"name" => name, "password" => "password"}} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:error, {:checkpw, false}} == res +  end + +  test "get_user/grant_type_password", %{user: user, name: name, password: password} do +    params = %{"grant_type" => "password", "username" => name, "password" => password} +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: params}) + +    assert {:ok, user} == res +  end + +  test "error credintails" do +    res = PleromaAuthenticator.get_user(%Plug.Conn{params: %{}}) +    assert {:error, :invalid_credentials} == res +  end +end diff --git a/test/web/auth/totp_authenticator_test.exs b/test/web/auth/totp_authenticator_test.exs new file mode 100644 index 000000000..e08069490 --- /dev/null +++ b/test/web/auth/totp_authenticator_test.exs @@ -0,0 +1,51 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Auth.TOTPAuthenticatorTest do +  use Pleroma.Web.ConnCase + +  alias Pleroma.MFA +  alias Pleroma.MFA.BackupCodes +  alias Pleroma.MFA.TOTP +  alias Pleroma.Web.Auth.TOTPAuthenticator + +  import Pleroma.Factory + +  test "verify token" do +    otp_secret = TOTP.generate_secret() +    otp_token = TOTP.generate_token(otp_secret) + +    user = +      insert(:user, +        multi_factor_authentication_settings: %MFA.Settings{ +          enabled: true, +          totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +        } +      ) + +    assert TOTPAuthenticator.verify(otp_token, user) == {:ok, :pass} +    assert TOTPAuthenticator.verify(nil, user) == {:error, :invalid_token} +    assert TOTPAuthenticator.verify("", user) == {:error, :invalid_token} +  end + +  test "checks backup codes" do +    [code | _] = backup_codes = BackupCodes.generate() + +    hashed_codes = +      backup_codes +      |> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1)) + +    user = +      insert(:user, +        multi_factor_authentication_settings: %MFA.Settings{ +          enabled: true, +          backup_codes: hashed_codes, +          totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true} +        } +      ) + +    assert TOTPAuthenticator.verify_recovery_code(user, code) == {:ok, :pass} +    refute TOTPAuthenticator.verify_recovery_code(code, refresh_record(user)) == {:ok, :pass} +  end +end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 74171fcd9..e5f7e3ef8 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -9,11 +9,13 @@ defmodule Pleroma.Web.CommonAPITest do    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  import Mock    require Pleroma.Constants @@ -21,6 +23,84 @@ defmodule Pleroma.Web.CommonAPITest do    setup do: clear_config([:instance, :limit])    setup do: clear_config([:instance, :max_pinned_statuses]) +  describe "deletion" do +    test "it allows users to delete their posts" do +      user = insert(:user) + +      {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + +      with_mock Pleroma.Web.Federator, +        publish: fn _ -> nil end do +        assert {:ok, delete} = CommonAPI.delete(post.id, user) +        assert delete.local +        assert called(Pleroma.Web.Federator.publish(delete)) +      end + +      refute Activity.get_by_id(post.id) +    end + +    test "it does not allow a user to delete their posts" do +      user = insert(:user) +      other_user = insert(:user) + +      {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + +      assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user) +      assert Activity.get_by_id(post.id) +    end + +    test "it allows moderators to delete other user's posts" do +      user = insert(:user) +      moderator = insert(:user, is_moderator: true) + +      {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + +      assert {:ok, delete} = CommonAPI.delete(post.id, moderator) +      assert delete.local + +      refute Activity.get_by_id(post.id) +    end + +    test "it allows admins to delete other user's posts" do +      user = insert(:user) +      moderator = insert(:user, is_admin: true) + +      {:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"}) + +      assert {:ok, delete} = CommonAPI.delete(post.id, moderator) +      assert delete.local + +      refute Activity.get_by_id(post.id) +    end + +    test "superusers deleting non-local posts won't federate the delete" do +      # This is the user of the ingested activity +      _user = +        insert(:user, +          local: false, +          ap_id: "http://mastodon.example.org/users/admin", +          last_refreshed_at: NaiveDateTime.utc_now() +        ) + +      moderator = insert(:user, is_admin: true) + +      data = +        File.read!("test/fixtures/mastodon-post-activity.json") +        |> Jason.decode!() + +      {:ok, post} = Transmogrifier.handle_incoming(data) + +      with_mock Pleroma.Web.Federator, +        publish: fn _ -> nil end do +        assert {:ok, delete} = CommonAPI.delete(post.id, moderator) +        assert delete.local +        refute called(Pleroma.Web.Federator.publish(:_)) +      end + +      refute Activity.get_by_id(post.id) +    end +  end +    test "favoriting race condition" do      user = insert(:user)      users_serial = insert_list(10, :user) diff --git a/test/web/mastodon_api/controllers/conversation_controller_test.exs b/test/web/mastodon_api/controllers/conversation_controller_test.exs index 801b0259b..04695572e 100644 --- a/test/web/mastodon_api/controllers/conversation_controller_test.exs +++ b/test/web/mastodon_api/controllers/conversation_controller_test.exs @@ -36,7 +36,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do      res_conn = get(conn, "/api/v1/conversations") -    assert response = json_response(res_conn, 200) +    assert response = json_response_and_validate_schema(res_conn, 200)      assert [               %{ @@ -91,18 +91,18 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do          "visibility" => "direct"        }) -    [conversation1, conversation2] = -      conn -      |> get("/api/v1/conversations", %{"recipients" => [user_two.id]}) -      |> json_response(200) +    assert [conversation1, conversation2] = +             conn +             |> get("/api/v1/conversations?recipients[]=#{user_two.id}") +             |> json_response_and_validate_schema(200)      assert conversation1["last_status"]["id"] == direct5.id      assert conversation2["last_status"]["id"] == direct1.id      [conversation1] =        conn -      |> get("/api/v1/conversations", %{"recipients" => [user_two.id, user_three.id]}) -      |> json_response(200) +      |> get("/api/v1/conversations?recipients[]=#{user_two.id}&recipients[]=#{user_three.id}") +      |> json_response_and_validate_schema(200)      assert conversation1["last_status"]["id"] == direct3.id    end @@ -126,7 +126,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do      [%{"last_status" => res_last_status}] =        conn        |> get("/api/v1/conversations") -      |> json_response(200) +      |> json_response_and_validate_schema(200)      assert res_last_status["id"] == direct_reply.id    end @@ -154,12 +154,12 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do      [%{"id" => direct_conversation_id, "unread" => true}] =        user_two_conn        |> get("/api/v1/conversations") -      |> json_response(200) +      |> json_response_and_validate_schema(200)      %{"unread" => false} =        user_two_conn        |> post("/api/v1/conversations/#{direct_conversation_id}/read") -      |> json_response(200) +      |> json_response_and_validate_schema(200)      assert User.get_cached_by_id(user_one.id).unread_conversation_count == 0      assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 @@ -175,7 +175,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationControllerTest do      [%{"unread" => true}] =        conn        |> get("/api/v1/conversations") -      |> json_response(200) +      |> json_response_and_validate_schema(200)      assert User.get_cached_by_id(user_one.id).unread_conversation_count == 1      assert User.get_cached_by_id(user_two.id).unread_conversation_count == 0 diff --git a/test/web/mastodon_api/controllers/filter_controller_test.exs b/test/web/mastodon_api/controllers/filter_controller_test.exs index 97ab005e0..f29547d13 100644 --- a/test/web/mastodon_api/controllers/filter_controller_test.exs +++ b/test/web/mastodon_api/controllers/filter_controller_test.exs @@ -15,9 +15,12 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do        context: ["home"]      } -    conn = post(conn, "/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) +    conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/v1/filters", %{"phrase" => filter.phrase, context: filter.context}) -    assert response = json_response(conn, 200) +    assert response = json_response_and_validate_schema(conn, 200)      assert response["phrase"] == filter.phrase      assert response["context"] == filter.context      assert response["irreversible"] == false @@ -48,12 +51,12 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do      response =        conn        |> get("/api/v1/filters") -      |> json_response(200) +      |> json_response_and_validate_schema(200)      assert response ==               render_json(                 FilterView, -               "filters.json", +               "index.json",                 filters: [filter_two, filter_one]               )    end @@ -72,7 +75,7 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do      conn = get(conn, "/api/v1/filters/#{filter.filter_id}") -    assert _response = json_response(conn, 200) +    assert response = json_response_and_validate_schema(conn, 200)    end    test "update a filter" do @@ -82,7 +85,8 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do        user_id: user.id,        filter_id: 2,        phrase: "knight", -      context: ["home"] +      context: ["home"], +      hide: true      }      {:ok, _filter} = Pleroma.Filter.create(query) @@ -93,14 +97,17 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do      }      conn = -      put(conn, "/api/v1/filters/#{query.filter_id}", %{ +      conn +      |> put_req_header("content-type", "application/json") +      |> put("/api/v1/filters/#{query.filter_id}", %{          phrase: new.phrase,          context: new.context        }) -    assert response = json_response(conn, 200) +    assert response = json_response_and_validate_schema(conn, 200)      assert response["phrase"] == new.phrase      assert response["context"] == new.context +    assert response["irreversible"] == true    end    test "delete a filter" do @@ -117,7 +124,6 @@ defmodule Pleroma.Web.MastodonAPI.FilterControllerTest do      conn = delete(conn, "/api/v1/filters/#{filter.filter_id}") -    assert response = json_response(conn, 200) -    assert response == %{} +    assert json_response_and_validate_schema(conn, 200) == %{}    end  end diff --git a/test/web/mastodon_api/controllers/follow_request_controller_test.exs b/test/web/mastodon_api/controllers/follow_request_controller_test.exs index d8dbe4800..44e12d15a 100644 --- a/test/web/mastodon_api/controllers/follow_request_controller_test.exs +++ b/test/web/mastodon_api/controllers/follow_request_controller_test.exs @@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do        conn = get(conn, "/api/v1/follow_requests") -      assert [relationship] = json_response(conn, 200) +      assert [relationship] = json_response_and_validate_schema(conn, 200)        assert to_string(other_user.id) == relationship["id"]      end @@ -44,7 +44,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do        conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/authorize") -      assert relationship = json_response(conn, 200) +      assert relationship = json_response_and_validate_schema(conn, 200)        assert to_string(other_user.id) == relationship["id"]        user = User.get_cached_by_id(user.id) @@ -62,7 +62,7 @@ defmodule Pleroma.Web.MastodonAPI.FollowRequestControllerTest do        conn = post(conn, "/api/v1/follow_requests/#{other_user.id}/reject") -      assert relationship = json_response(conn, 200) +      assert relationship = json_response_and_validate_schema(conn, 200)        assert to_string(other_user.id) == relationship["id"]        user = User.get_cached_by_id(user.id) diff --git a/test/web/mastodon_api/controllers/instance_controller_test.exs b/test/web/mastodon_api/controllers/instance_controller_test.exs index 2c7fd9fd0..90840d5ab 100644 --- a/test/web/mastodon_api/controllers/instance_controller_test.exs +++ b/test/web/mastodon_api/controllers/instance_controller_test.exs @@ -10,7 +10,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do    test "get instance information", %{conn: conn} do      conn = get(conn, "/api/v1/instance") -    assert result = json_response(conn, 200) +    assert result = json_response_and_validate_schema(conn, 200)      email = Pleroma.Config.get([:instance, :email])      # Note: not checking for "max_toot_chars" since it's optional @@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do      conn = get(conn, "/api/v1/instance") -    assert result = json_response(conn, 200) +    assert result = json_response_and_validate_schema(conn, 200)      stats = result["stats"] @@ -74,7 +74,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do      conn = get(conn, "/api/v1/instance/peers") -    assert result = json_response(conn, 200) +    assert result = json_response_and_validate_schema(conn, 200)      assert ["peer1.com", "peer2.com"] == Enum.sort(result)    end diff --git a/test/web/mastodon_api/controllers/list_controller_test.exs b/test/web/mastodon_api/controllers/list_controller_test.exs index c9c4cbb49..57a9ef4a4 100644 --- a/test/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/web/mastodon_api/controllers/list_controller_test.exs @@ -12,37 +12,44 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do    test "creating a list" do      %{conn: conn} = oauth_access(["write:lists"]) -    conn = post(conn, "/api/v1/lists", %{"title" => "cuties"}) - -    assert %{"title" => title} = json_response(conn, 200) -    assert title == "cuties" +    assert %{"title" => "cuties"} = +             conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/lists", %{"title" => "cuties"}) +             |> json_response_and_validate_schema(:ok)    end    test "renders error for invalid params" do      %{conn: conn} = oauth_access(["write:lists"]) -    conn = post(conn, "/api/v1/lists", %{"title" => nil}) +    conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/v1/lists", %{"title" => nil}) -    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) +    assert %{"error" => "title - null value where string expected."} = +             json_response_and_validate_schema(conn, 400)    end    test "listing a user's lists" do      %{conn: conn} = oauth_access(["read:lists", "write:lists"])      conn +    |> put_req_header("content-type", "application/json")      |> post("/api/v1/lists", %{"title" => "cuties"}) -    |> json_response(:ok) +    |> json_response_and_validate_schema(:ok)      conn +    |> put_req_header("content-type", "application/json")      |> post("/api/v1/lists", %{"title" => "cofe"}) -    |> json_response(:ok) +    |> json_response_and_validate_schema(:ok)      conn = get(conn, "/api/v1/lists")      assert [               %{"id" => _, "title" => "cofe"},               %{"id" => _, "title" => "cuties"} -           ] = json_response(conn, :ok) +           ] = json_response_and_validate_schema(conn, :ok)    end    test "adding users to a list" do @@ -50,9 +57,12 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do      other_user = insert(:user)      {:ok, list} = Pleroma.List.create("name", user) -    conn = post(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) +    assert %{} == +             conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) +             |> json_response_and_validate_schema(:ok) -    assert %{} == json_response(conn, 200)      %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)      assert following == [other_user.follower_address]    end @@ -65,9 +75,12 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do      {:ok, list} = Pleroma.List.follow(list, other_user)      {:ok, list} = Pleroma.List.follow(list, third_user) -    conn = delete(conn, "/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) +    assert %{} == +             conn +             |> put_req_header("content-type", "application/json") +             |> delete("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) +             |> json_response_and_validate_schema(:ok) -    assert %{} == json_response(conn, 200)      %Pleroma.List{following: following} = Pleroma.List.get(list.id, user)      assert following == [third_user.follower_address]    end @@ -83,7 +96,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do        |> assign(:user, user)        |> get("/api/v1/lists/#{list.id}/accounts", %{"account_ids" => [other_user.id]}) -    assert [%{"id" => id}] = json_response(conn, 200) +    assert [%{"id" => id}] = json_response_and_validate_schema(conn, 200)      assert id == to_string(other_user.id)    end @@ -96,7 +109,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do        |> assign(:user, user)        |> get("/api/v1/lists/#{list.id}") -    assert %{"id" => id} = json_response(conn, 200) +    assert %{"id" => id} = json_response_and_validate_schema(conn, 200)      assert id == to_string(list.id)    end @@ -105,17 +118,18 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do      conn = get(conn, "/api/v1/lists/666") -    assert %{"error" => "List not found"} = json_response(conn, :not_found) +    assert %{"error" => "List not found"} = json_response_and_validate_schema(conn, :not_found)    end    test "renaming a list" do      %{user: user, conn: conn} = oauth_access(["write:lists"])      {:ok, list} = Pleroma.List.create("name", user) -    conn = put(conn, "/api/v1/lists/#{list.id}", %{"title" => "newname"}) - -    assert %{"title" => name} = json_response(conn, 200) -    assert name == "newname" +    assert %{"title" => "newname"} = +             conn +             |> put_req_header("content-type", "application/json") +             |> put("/api/v1/lists/#{list.id}", %{"title" => "newname"}) +             |> json_response_and_validate_schema(:ok)    end    test "validates title when renaming a list" do @@ -125,9 +139,11 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do      conn =        conn        |> assign(:user, user) +      |> put_req_header("content-type", "application/json")        |> put("/api/v1/lists/#{list.id}", %{"title" => "  "}) -    assert %{"error" => "can't be blank"} == json_response(conn, :unprocessable_entity) +    assert %{"error" => "can't be blank"} == +             json_response_and_validate_schema(conn, :unprocessable_entity)    end    test "deleting a list" do @@ -136,7 +152,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do      conn = delete(conn, "/api/v1/lists/#{list.id}") -    assert %{} = json_response(conn, 200) +    assert %{} = json_response_and_validate_schema(conn, 200)      assert is_nil(Repo.get(Pleroma.List, list.id))    end  end diff --git a/test/web/mastodon_api/controllers/marker_controller_test.exs b/test/web/mastodon_api/controllers/marker_controller_test.exs index 919f295bd..bce719bea 100644 --- a/test/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/web/mastodon_api/controllers/marker_controller_test.exs @@ -22,8 +22,8 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do          conn          |> assign(:user, user)          |> assign(:token, token) -        |> get("/api/v1/markers", %{timeline: ["notifications"]}) -        |> json_response(200) +        |> get("/api/v1/markers?timeline[]=notifications") +        |> json_response_and_validate_schema(200)        assert response == %{                 "notifications" => %{ @@ -45,7 +45,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do          |> assign(:user, user)          |> assign(:token, token)          |> get("/api/v1/markers", %{timeline: ["notifications"]}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        assert response == %{"error" => "Insufficient permissions: read:statuses."}      end @@ -60,11 +60,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do          conn          |> assign(:user, user)          |> assign(:token, token) +        |> put_req_header("content-type", "application/json")          |> post("/api/v1/markers", %{            home: %{last_read_id: "777"},            notifications: %{"last_read_id" => "69420"}          }) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        assert %{                 "notifications" => %{ @@ -89,11 +90,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do          conn          |> assign(:user, user)          |> assign(:token, token) +        |> put_req_header("content-type", "application/json")          |> post("/api/v1/markers", %{            home: %{last_read_id: "777"},            notifications: %{"last_read_id" => "69888"}          }) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        assert response == %{                 "notifications" => %{ @@ -112,11 +114,12 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do          conn          |> assign(:user, user)          |> assign(:token, token) +        |> put_req_header("content-type", "application/json")          |> post("/api/v1/markers", %{            home: %{last_read_id: "777"},            notifications: %{"last_read_id" => "69420"}          }) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        assert response == %{"error" => "Insufficient permissions: write:statuses."}      end diff --git a/test/web/mastodon_api/controllers/poll_controller_test.exs b/test/web/mastodon_api/controllers/poll_controller_test.exs index 88b13a25a..d8f34aa86 100644 --- a/test/web/mastodon_api/controllers/poll_controller_test.exs +++ b/test/web/mastodon_api/controllers/poll_controller_test.exs @@ -24,7 +24,7 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        conn = get(conn, "/api/v1/polls/#{object.id}") -      response = json_response(conn, 200) +      response = json_response_and_validate_schema(conn, 200)        id = to_string(object.id)        assert %{"id" => ^id, "expired" => false, "multiple" => false} = response      end @@ -43,7 +43,7 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        conn = get(conn, "/api/v1/polls/#{object.id}") -      assert json_response(conn, 404) +      assert json_response_and_validate_schema(conn, 404)      end    end @@ -65,9 +65,12 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        object = Object.normalize(activity) -      conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1, 2]}) -      assert json_response(conn, 200) +      assert json_response_and_validate_schema(conn, 200)        object = Object.get_by_id(object.id)        assert Enum.all?(object.data["anyOf"], fn %{"replies" => %{"totalItems" => total_items}} -> @@ -85,8 +88,9 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        object = Object.normalize(activity)        assert conn +             |> put_req_header("content-type", "application/json")               |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [1]}) -             |> json_response(422) == %{"error" => "Poll's author can't vote"} +             |> json_response_and_validate_schema(422) == %{"error" => "Poll's author can't vote"}        object = Object.get_by_id(object.id) @@ -105,8 +109,9 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        object = Object.normalize(activity)        assert conn +             |> put_req_header("content-type", "application/json")               |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0, 1]}) -             |> json_response(422) == %{"error" => "Too many choices"} +             |> json_response_and_validate_schema(422) == %{"error" => "Too many choices"}        object = Object.get_by_id(object.id) @@ -126,15 +131,21 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        object = Object.normalize(activity) -      conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [2]}) -      assert json_response(conn, 422) == %{"error" => "Invalid indices"} +      assert json_response_and_validate_schema(conn, 422) == %{"error" => "Invalid indices"}      end      test "returns 404 error when object is not exist", %{conn: conn} do -      conn = post(conn, "/api/v1/polls/1/votes", %{"choices" => [0]}) +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/v1/polls/1/votes", %{"choices" => [0]}) -      assert json_response(conn, 404) == %{"error" => "Record not found"} +      assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}      end      test "returns 404 when poll is private and not available for user", %{conn: conn} do @@ -149,9 +160,12 @@ defmodule Pleroma.Web.MastodonAPI.PollControllerTest do        object = Object.normalize(activity) -      conn = post(conn, "/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/v1/polls/#{object.id}/votes", %{"choices" => [0]}) -      assert json_response(conn, 404) == %{"error" => "Record not found"} +      assert json_response_and_validate_schema(conn, 404) == %{"error" => "Record not found"}      end    end  end diff --git a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs index f86274d57..1ff871c89 100644 --- a/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs +++ b/test/web/mastodon_api/controllers/scheduled_activity_controller_test.exs @@ -24,19 +24,19 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do      # min_id      conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&min_id=#{scheduled_activity_id1}") -    result = json_response(conn_res, 200) +    result = json_response_and_validate_schema(conn_res, 200)      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result      # since_id      conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&since_id=#{scheduled_activity_id1}") -    result = json_response(conn_res, 200) +    result = json_response_and_validate_schema(conn_res, 200)      assert [%{"id" => ^scheduled_activity_id4}, %{"id" => ^scheduled_activity_id3}] = result      # max_id      conn_res = get(conn, "/api/v1/scheduled_statuses?limit=2&max_id=#{scheduled_activity_id4}") -    result = json_response(conn_res, 200) +    result = json_response_and_validate_schema(conn_res, 200)      assert [%{"id" => ^scheduled_activity_id3}, %{"id" => ^scheduled_activity_id2}] = result    end @@ -46,12 +46,12 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do      res_conn = get(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}") -    assert %{"id" => scheduled_activity_id} = json_response(res_conn, 200) +    assert %{"id" => scheduled_activity_id} = json_response_and_validate_schema(res_conn, 200)      assert scheduled_activity_id == scheduled_activity.id |> to_string()      res_conn = get(conn, "/api/v1/scheduled_statuses/404") -    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)    end    test "updates a scheduled activity" do @@ -74,22 +74,32 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do      assert job.args == %{"activity_id" => scheduled_activity.id}      assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(scheduled_at) -    new_scheduled_at = Timex.shift(NaiveDateTime.utc_now(), minutes: 120) +    new_scheduled_at = +      NaiveDateTime.utc_now() +      |> Timex.shift(minutes: 120) +      |> Timex.format!("%Y-%m-%dT%H:%M:%S.%fZ", :strftime)      res_conn = -      put(conn, "/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{ +      conn +      |> put_req_header("content-type", "application/json") +      |> put("/api/v1/scheduled_statuses/#{scheduled_activity.id}", %{          scheduled_at: new_scheduled_at        }) -    assert %{"scheduled_at" => expected_scheduled_at} = json_response(res_conn, 200) +    assert %{"scheduled_at" => expected_scheduled_at} = +             json_response_and_validate_schema(res_conn, 200) +      assert expected_scheduled_at == Pleroma.Web.CommonAPI.Utils.to_masto_date(new_scheduled_at)      job = refresh_record(job)      assert DateTime.truncate(job.scheduled_at, :second) == to_datetime(new_scheduled_at) -    res_conn = put(conn, "/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) +    res_conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> put("/api/v1/scheduled_statuses/404", %{scheduled_at: new_scheduled_at}) -    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)    end    test "deletes a scheduled activity" do @@ -115,7 +125,7 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do        |> assign(:user, user)        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") -    assert %{} = json_response(res_conn, 200) +    assert %{} = json_response_and_validate_schema(res_conn, 200)      refute Repo.get(ScheduledActivity, scheduled_activity.id)      refute Repo.get(Oban.Job, job.id) @@ -124,6 +134,6 @@ defmodule Pleroma.Web.MastodonAPI.ScheduledActivityControllerTest do        |> assign(:user, user)        |> delete("/api/v1/scheduled_statuses/#{scheduled_activity.id}") -    assert %{"error" => "Record not found"} = json_response(res_conn, 404) +    assert %{"error" => "Record not found"} = json_response_and_validate_schema(res_conn, 404)    end  end diff --git a/test/web/mastodon_api/controllers/subscription_controller_test.exs b/test/web/mastodon_api/controllers/subscription_controller_test.exs index 5682498c0..4aa260663 100644 --- a/test/web/mastodon_api/controllers/subscription_controller_test.exs +++ b/test/web/mastodon_api/controllers/subscription_controller_test.exs @@ -6,6 +6,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory +    alias Pleroma.Web.Push    alias Pleroma.Web.Push.Subscription @@ -27,6 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        build_conn()        |> assign(:user, user)        |> assign(:token, token) +      |> put_req_header("content-type", "application/json")      %{conn: conn, user: user, token: token}    end @@ -47,8 +49,8 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do      test "returns error when push disabled ", %{conn: conn} do        assert_error_when_disable_push do          conn -        |> post("/api/v1/push/subscription", %{}) -        |> json_response(403) +        |> post("/api/v1/push/subscription", %{subscription: @sub}) +        |> json_response_and_validate_schema(403)        end      end @@ -59,7 +61,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do            "data" => %{"alerts" => %{"mention" => true, "test" => true}},            "subscription" => @sub          }) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        [subscription] = Pleroma.Repo.all(Subscription) @@ -77,7 +79,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        assert_error_when_disable_push do          conn          |> get("/api/v1/push/subscription", %{}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        end      end @@ -85,9 +87,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> get("/api/v1/push/subscription", %{}) -        |> json_response(404) +        |> json_response_and_validate_schema(404) -      assert "Not found" == res +      assert %{"error" => "Record not found"} == res      end      test "returns a user subsciption", %{conn: conn, user: user, token: token} do @@ -101,7 +103,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> get("/api/v1/push/subscription", %{}) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        expect = %{          "alerts" => %{"mention" => true}, @@ -130,7 +132,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        assert_error_when_disable_push do          conn          |> put("/api/v1/push/subscription", %{data: %{"alerts" => %{"mention" => false}}}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        end      end @@ -140,7 +142,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do          |> put("/api/v1/push/subscription", %{            data: %{"alerts" => %{"mention" => false, "follow" => true}}          }) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        expect = %{          "alerts" => %{"follow" => true, "mention" => false}, @@ -158,7 +160,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        assert_error_when_disable_push do          conn          |> delete("/api/v1/push/subscription", %{}) -        |> json_response(403) +        |> json_response_and_validate_schema(403)        end      end @@ -166,9 +168,9 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> delete("/api/v1/push/subscription", %{}) -        |> json_response(404) +        |> json_response_and_validate_schema(404) -      assert "Not found" == res +      assert %{"error" => "Record not found"} == res      end      test "returns empty result and delete user subsciption", %{ @@ -186,7 +188,7 @@ defmodule Pleroma.Web.MastodonAPI.SubscriptionControllerTest do        res =          conn          |> delete("/api/v1/push/subscription", %{}) -        |> json_response(200) +        |> json_response_and_validate_schema(200)        assert %{} == res        refute Pleroma.Repo.get(Subscription, subscription.id) diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index b64370c3f..b5e7dc317 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -402,11 +402,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do        pleroma: %{mime_type: "image/png"}      } +    api_spec = Pleroma.Web.ApiSpec.spec() +      assert expected == StatusView.render("attachment.json", %{attachment: object}) +    OpenApiSpex.TestAssertions.assert_schema(expected, "Attachment", api_spec)      # If theres a "id", use that instead of the generated one      object = Map.put(object, "id", 2) -    assert %{id: "2"} = StatusView.render("attachment.json", %{attachment: object}) +    result = StatusView.render("attachment.json", %{attachment: object}) + +    assert %{id: "2"} = result +    OpenApiSpex.TestAssertions.assert_schema(result, "Attachment", api_spec)    end    test "put the url advertised in the Activity in to the url attribute" do diff --git a/test/web/oauth/mfa_controller_test.exs b/test/web/oauth/mfa_controller_test.exs new file mode 100644 index 000000000..ce4a07320 --- /dev/null +++ b/test/web/oauth/mfa_controller_test.exs @@ -0,0 +1,306 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.OAuth.MFAControllerTest do +  use Pleroma.Web.ConnCase +  import Pleroma.Factory + +  alias Pleroma.MFA +  alias Pleroma.MFA.BackupCodes +  alias Pleroma.MFA.TOTP +  alias Pleroma.Repo +  alias Pleroma.Web.OAuth.Authorization +  alias Pleroma.Web.OAuth.OAuthController + +  setup %{conn: conn} do +    otp_secret = TOTP.generate_secret() + +    user = +      insert(:user, +        multi_factor_authentication_settings: %MFA.Settings{ +          enabled: true, +          backup_codes: [Comeonin.Pbkdf2.hashpwsalt("test-code")], +          totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +        } +      ) + +    app = insert(:oauth_app) +    {:ok, conn: conn, user: user, app: app} +  end + +  describe "show" do +    setup %{conn: conn, user: user, app: app} do +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      {:ok, conn: conn, mfa_token: mfa_token} +    end + +    test "GET /oauth/mfa renders mfa forms", %{conn: conn, mfa_token: mfa_token} do +      conn = +        get( +          conn, +          "/oauth/mfa", +          %{ +            "mfa_token" => mfa_token.token, +            "state" => "a_state", +            "redirect_uri" => "http://localhost:8080/callback" +          } +        ) + +      assert response = html_response(conn, 200) +      assert response =~ "Two-factor authentication" +      assert response =~ mfa_token.token +      assert response =~ "http://localhost:8080/callback" +    end + +    test "GET /oauth/mfa renders mfa recovery forms", %{conn: conn, mfa_token: mfa_token} do +      conn = +        get( +          conn, +          "/oauth/mfa", +          %{ +            "mfa_token" => mfa_token.token, +            "state" => "a_state", +            "redirect_uri" => "http://localhost:8080/callback", +            "challenge_type" => "recovery" +          } +        ) + +      assert response = html_response(conn, 200) +      assert response =~ "Two-factor recovery" +      assert response =~ mfa_token.token +      assert response =~ "http://localhost:8080/callback" +    end +  end + +  describe "verify" do +    setup %{conn: conn, user: user, app: app} do +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      {:ok, conn: conn, user: user, mfa_token: mfa_token, app: app} +    end + +    test "POST /oauth/mfa/verify, verify totp code", %{ +      conn: conn, +      user: user, +      mfa_token: mfa_token, +      app: app +    } do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + +      conn = +        conn +        |> post("/oauth/mfa/verify", %{ +          "mfa" => %{ +            "mfa_token" => mfa_token.token, +            "challenge_type" => "totp", +            "code" => otp_token, +            "state" => "a_state", +            "redirect_uri" => OAuthController.default_redirect_uri(app) +          } +        }) + +      target = redirected_to(conn) +      target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string() +      query = URI.parse(target).query |> URI.query_decoder() |> Map.new() +      assert %{"state" => "a_state", "code" => code} = query +      assert target_url == OAuthController.default_redirect_uri(app) +      auth = Repo.get_by(Authorization, token: code) +      assert auth.scopes == ["write"] +    end + +    test "POST /oauth/mfa/verify, verify recovery code", %{ +      conn: conn, +      mfa_token: mfa_token, +      app: app +    } do +      conn = +        conn +        |> post("/oauth/mfa/verify", %{ +          "mfa" => %{ +            "mfa_token" => mfa_token.token, +            "challenge_type" => "recovery", +            "code" => "test-code", +            "state" => "a_state", +            "redirect_uri" => OAuthController.default_redirect_uri(app) +          } +        }) + +      target = redirected_to(conn) +      target_url = %URI{URI.parse(target) | query: nil} |> URI.to_string() +      query = URI.parse(target).query |> URI.query_decoder() |> Map.new() +      assert %{"state" => "a_state", "code" => code} = query +      assert target_url == OAuthController.default_redirect_uri(app) +      auth = Repo.get_by(Authorization, token: code) +      assert auth.scopes == ["write"] +    end +  end + +  describe "challenge/totp" do +    test "returns access token with valid code", %{conn: conn, user: user, app: app} do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "totp", +          "code" => otp_token, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(:ok) + +      ap_id = user.ap_id + +      assert match?( +               %{ +                 "access_token" => _, +                 "expires_in" => 600, +                 "me" => ^ap_id, +                 "refresh_token" => _, +                 "scope" => "write", +                 "token_type" => "Bearer" +               }, +               response +             ) +    end + +    test "returns errors when mfa token invalid", %{conn: conn, user: user, app: app} do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => "XXX", +          "challenge_type" => "totp", +          "code" => otp_token, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(400) + +      assert response == %{"error" => "Invalid code"} +    end + +    test "returns error when otp code is invalid", %{conn: conn, user: user, app: app} do +      mfa_token = insert(:mfa_token, user: user) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "totp", +          "code" => "XXX", +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(400) + +      assert response == %{"error" => "Invalid code"} +    end + +    test "returns error when client credentails is wrong ", %{conn: conn, user: user} do +      otp_token = TOTP.generate_token(user.multi_factor_authentication_settings.totp.secret) +      mfa_token = insert(:mfa_token, user: user) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "totp", +          "code" => otp_token, +          "client_id" => "xxx", +          "client_secret" => "xxx" +        }) +        |> json_response(400) + +      assert response == %{"error" => "Invalid code"} +    end +  end + +  describe "challenge/recovery" do +    setup %{conn: conn} do +      app = insert(:oauth_app) +      {:ok, conn: conn, app: app} +    end + +    test "returns access token with valid code", %{conn: conn, app: app} do +      otp_secret = TOTP.generate_secret() + +      [code | _] = backup_codes = BackupCodes.generate() + +      hashed_codes = +        backup_codes +        |> Enum.map(&Comeonin.Pbkdf2.hashpwsalt(&1)) + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            backup_codes: hashed_codes, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      mfa_token = +        insert(:mfa_token, +          user: user, +          authorization: build(:oauth_authorization, app: app, scopes: ["write"]) +        ) + +      response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "recovery", +          "code" => code, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(:ok) + +      ap_id = user.ap_id + +      assert match?( +               %{ +                 "access_token" => _, +                 "expires_in" => 600, +                 "me" => ^ap_id, +                 "refresh_token" => _, +                 "scope" => "write", +                 "token_type" => "Bearer" +               }, +               response +             ) + +      error_response = +        conn +        |> post("/oauth/mfa/challenge", %{ +          "mfa_token" => mfa_token.token, +          "challenge_type" => "recovery", +          "code" => code, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(400) + +      assert error_response == %{"error" => "Invalid code"} +    end +  end +end diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index f2f98d768..7a107584d 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -6,6 +6,8 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do    use Pleroma.Web.ConnCase    import Pleroma.Factory +  alias Pleroma.MFA +  alias Pleroma.MFA.TOTP    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.Web.OAuth.Authorization @@ -604,6 +606,41 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        end      end +    test "redirect to on two-factor auth page" do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      app = insert(:oauth_app, scopes: ["read", "write", "follow"]) + +      conn = +        build_conn() +        |> post("/oauth/authorize", %{ +          "authorization" => %{ +            "name" => user.nickname, +            "password" => "test", +            "client_id" => app.client_id, +            "redirect_uri" => app.redirect_uris, +            "scope" => "read write", +            "state" => "statepassed" +          } +        }) + +      result = html_response(conn, 200) + +      mfa_token = Repo.get_by(MFA.Token, user_id: user.id) +      assert result =~ app.redirect_uris +      assert result =~ "statepassed" +      assert result =~ mfa_token.token +      assert result =~ "Two-factor authentication" +    end +      test "returns 401 for wrong credentials", %{conn: conn} do        user = insert(:user)        app = insert(:oauth_app) @@ -735,6 +772,46 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do        assert token.scopes == app.scopes      end +    test "issues a mfa token for `password` grant_type, when MFA enabled" do +      password = "testpassword" +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          password_hash: Comeonin.Pbkdf2.hashpwsalt(password), +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      app = insert(:oauth_app, scopes: ["read", "write"]) + +      response = +        build_conn() +        |> post("/oauth/token", %{ +          "grant_type" => "password", +          "username" => user.nickname, +          "password" => password, +          "client_id" => app.client_id, +          "client_secret" => app.client_secret +        }) +        |> json_response(403) + +      assert match?( +               %{ +                 "supported_challenge_types" => "totp", +                 "mfa_token" => _, +                 "error" => "mfa_required" +               }, +               response +             ) + +      token = Repo.get_by(MFA.Token, token: response["mfa_token"]) +      assert token.user_id == user.id +      assert token.authorization_id +    end +      test "issues a token for request with HTTP basic auth client credentials" do        user = insert(:user)        app = insert(:oauth_app, scopes: ["scope1", "scope2", "scope3"]) diff --git a/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs b/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs new file mode 100644 index 000000000..d23d08a00 --- /dev/null +++ b/test/web/pleroma_api/controllers/two_factor_authentication_controller_test.exs @@ -0,0 +1,260 @@ +defmodule Pleroma.Web.PleromaAPI.TwoFactorAuthenticationControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  alias Pleroma.MFA.Settings +  alias Pleroma.MFA.TOTP + +  describe "GET /api/pleroma/accounts/mfa/settings" do +    test "returns user mfa settings for new user", %{conn: conn} do +      token = insert(:oauth_token, scopes: ["read", "follow"]) +      token2 = insert(:oauth_token, scopes: ["write"]) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> get("/api/pleroma/accounts/mfa") +             |> json_response(:ok) == %{ +               "settings" => %{"enabled" => false, "totp" => false} +             } + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> get("/api/pleroma/accounts/mfa") +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: read:security." +             } +    end + +    test "returns user mfa settings with enabled totp", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            enabled: true, +            totp: %Settings.TOTP{secret: "XXX", delivery_type: "app", confirmed: true} +          } +        ) + +      token = insert(:oauth_token, scopes: ["read", "follow"], user: user) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> get("/api/pleroma/accounts/mfa") +             |> json_response(:ok) == %{ +               "settings" => %{"enabled" => true, "totp" => true} +             } +    end +  end + +  describe "GET /api/pleroma/accounts/mfa/backup_codes" do +    test "returns backup codes", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: "secret"} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> get("/api/pleroma/accounts/mfa/backup_codes") +        |> json_response(:ok) + +      assert [<<_::bytes-size(6)>>, <<_::bytes-size(6)>>] = response["codes"] +      user = refresh_record(user) +      mfa_settings = user.multi_factor_authentication_settings +      assert mfa_settings.totp.secret == "secret" +      refute mfa_settings.backup_codes == ["1", "2", "3"] +      refute mfa_settings.backup_codes == [] + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> get("/api/pleroma/accounts/mfa/backup_codes") +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end + +  describe "GET /api/pleroma/accounts/mfa/setup/totp" do +    test "return errors when method is invalid", %{conn: conn} do +      user = insert(:user) +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> get("/api/pleroma/accounts/mfa/setup/torf") +        |> json_response(400) + +      assert response == %{"error" => "undefined method"} +    end + +    test "returns key and provisioning_uri", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{backup_codes: ["1", "2", "3"]} +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> get("/api/pleroma/accounts/mfa/setup/totp") +        |> json_response(:ok) + +      user = refresh_record(user) +      mfa_settings = user.multi_factor_authentication_settings +      secret = mfa_settings.totp.secret +      refute mfa_settings.enabled +      assert mfa_settings.backup_codes == ["1", "2", "3"] + +      assert response == %{ +               "key" => secret, +               "provisioning_uri" => TOTP.provisioning_uri(secret, "#{user.email}") +             } + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> get("/api/pleroma/accounts/mfa/setup/totp") +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end + +  describe "GET /api/pleroma/accounts/mfa/confirm/totp" do +    test "returns success result", %{conn: conn} do +      secret = TOTP.generate_secret() +      code = TOTP.generate_token(secret) + +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: secret} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code}) +             |> json_response(:ok) + +      settings = refresh_record(user).multi_factor_authentication_settings +      assert settings.enabled +      assert settings.totp.secret == secret +      assert settings.totp.confirmed +      assert settings.backup_codes == ["1", "2", "3"] + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: code}) +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end + +    test "returns error if password incorrect", %{conn: conn} do +      secret = TOTP.generate_secret() +      code = TOTP.generate_token(secret) + +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: secret} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "xxx", code: code}) +        |> json_response(422) + +      settings = refresh_record(user).multi_factor_authentication_settings +      refute settings.enabled +      refute settings.totp.confirmed +      assert settings.backup_codes == ["1", "2", "3"] +      assert response == %{"error" => "Invalid password."} +    end + +    test "returns error if code incorrect", %{conn: conn} do +      secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: secret} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      response = +        conn +        |> put_req_header("authorization", "Bearer #{token.token}") +        |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"}) +        |> json_response(422) + +      settings = refresh_record(user).multi_factor_authentication_settings +      refute settings.enabled +      refute settings.totp.confirmed +      assert settings.backup_codes == ["1", "2", "3"] +      assert response == %{"error" => "invalid_token"} + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> post("/api/pleroma/accounts/mfa/confirm/totp", %{password: "test", code: "code"}) +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end + +  describe "DELETE /api/pleroma/accounts/mfa/totp" do +    test "returns success result", %{conn: conn} do +      user = +        insert(:user, +          multi_factor_authentication_settings: %Settings{ +            backup_codes: ["1", "2", "3"], +            totp: %Settings.TOTP{secret: "secret"} +          } +        ) + +      token = insert(:oauth_token, scopes: ["write", "follow"], user: user) +      token2 = insert(:oauth_token, scopes: ["read"]) + +      assert conn +             |> put_req_header("authorization", "Bearer #{token.token}") +             |> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"}) +             |> json_response(:ok) + +      settings = refresh_record(user).multi_factor_authentication_settings +      refute settings.enabled +      assert settings.totp.secret == nil +      refute settings.totp.confirmed + +      assert conn +             |> put_req_header("authorization", "Bearer #{token2.token}") +             |> delete("/api/pleroma/accounts/mfa/totp", %{password: "test"}) +             |> json_response(403) == %{ +               "error" => "Insufficient permissions: write:security." +             } +    end +  end +end diff --git a/test/web/plugs/plug_test.exs b/test/web/plugs/plug_test.exs new file mode 100644 index 000000000..943e484e7 --- /dev/null +++ b/test/web/plugs/plug_test.exs @@ -0,0 +1,91 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PlugTest do +  @moduledoc "Tests for the functionality added via `use Pleroma.Web, :plug`" + +  alias Pleroma.Plugs.ExpectAuthenticatedCheckPlug +  alias Pleroma.Plugs.ExpectPublicOrAuthenticatedCheckPlug +  alias Pleroma.Plugs.PlugHelper + +  import Mock + +  use Pleroma.Web.ConnCase + +  describe "when plug is skipped, " do +    setup_with_mocks( +      [ +        {ExpectPublicOrAuthenticatedCheckPlug, [:passthrough], []} +      ], +      %{conn: conn} +    ) do +      conn = ExpectPublicOrAuthenticatedCheckPlug.skip_plug(conn) +      %{conn: conn} +    end + +    test "it neither adds plug to called plugs list nor calls `perform/2`, " <> +           "regardless of :if_func / :unless_func options", +         %{conn: conn} do +      for opts <- [%{}, %{if_func: fn _ -> true end}, %{unless_func: fn _ -> false end}] do +        ret_conn = ExpectPublicOrAuthenticatedCheckPlug.call(conn, opts) + +        refute called(ExpectPublicOrAuthenticatedCheckPlug.perform(:_, :_)) +        refute PlugHelper.plug_called?(ret_conn, ExpectPublicOrAuthenticatedCheckPlug) +      end +    end +  end + +  describe "when plug is NOT skipped, " do +    setup_with_mocks([{ExpectAuthenticatedCheckPlug, [:passthrough], []}]) do +      :ok +    end + +    test "with no pre-run checks, adds plug to called plugs list and calls `perform/2`", %{ +      conn: conn +    } do +      ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{}) + +      assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_)) +      assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) +    end + +    test "when :if_func option is given, calls the plug only if provided function evals tru-ish", +         %{conn: conn} do +      ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> false end}) + +      refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_)) +      refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + +      ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{if_func: fn _ -> true end}) + +      assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_)) +      assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) +    end + +    test "if :unless_func option is given, calls the plug only if provided function evals falsy", +         %{conn: conn} do +      ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> true end}) + +      refute called(ExpectAuthenticatedCheckPlug.perform(:_, :_)) +      refute PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) + +      ret_conn = ExpectAuthenticatedCheckPlug.call(conn, %{unless_func: fn _ -> false end}) + +      assert called(ExpectAuthenticatedCheckPlug.perform(ret_conn, :_)) +      assert PlugHelper.plug_called?(ret_conn, ExpectAuthenticatedCheckPlug) +    end + +    test "allows a plug to be called multiple times (even if it's in called plugs list)", %{ +      conn: conn +    } do +      conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value1}) +      assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value1})) + +      assert PlugHelper.plug_called?(conn, ExpectAuthenticatedCheckPlug) + +      conn = ExpectAuthenticatedCheckPlug.call(conn, %{an_option: :value2}) +      assert called(ExpectAuthenticatedCheckPlug.perform(conn, %{an_option: :value2})) +    end +  end +end diff --git a/test/web/streamer/ping_test.exs b/test/web/streamer/ping_test.exs deleted file mode 100644 index 5df6c1cc3..000000000 --- a/test/web/streamer/ping_test.exs +++ /dev/null @@ -1,36 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.PingTest do -  use Pleroma.DataCase - -  import Pleroma.Factory -  alias Pleroma.Web.Streamer - -  setup do -    start_supervised({Streamer.supervisor(), [ping_interval: 30]}) - -    :ok -  end - -  describe "sockets" do -    setup do -      user = insert(:user) -      {:ok, %{user: user}} -    end - -    test "it sends pings", %{user: user} do -      task = -        Task.async(fn -> -          assert_receive {:text, received_event}, 40 -          assert_receive {:text, received_event}, 40 -          assert_receive {:text, received_event}, 40 -        end) - -      Streamer.add_socket("public", %{transport_pid: task.pid, assigns: %{user: user}}) - -      Task.await(task) -    end -  end -end diff --git a/test/web/streamer/state_test.exs b/test/web/streamer/state_test.exs deleted file mode 100644 index a755e75c0..000000000 --- a/test/web/streamer/state_test.exs +++ /dev/null @@ -1,54 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.StateTest do -  use Pleroma.DataCase - -  import Pleroma.Factory -  alias Pleroma.Web.Streamer -  alias Pleroma.Web.Streamer.StreamerSocket - -  @moduletag needs_streamer: true - -  describe "sockets" do -    setup do -      user = insert(:user) -      user2 = insert(:user) -      {:ok, %{user: user, user2: user2}} -    end - -    test "it can add a socket", %{user: user} do -      Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) - -      assert(%{"public" => [%StreamerSocket{transport_pid: 1}]} = Streamer.get_sockets()) -    end - -    test "it can add multiple sockets per user", %{user: user} do -      Streamer.add_socket("public", %{transport_pid: 1, assigns: %{user: user}}) -      Streamer.add_socket("public", %{transport_pid: 2, assigns: %{user: user}}) - -      assert( -        %{ -          "public" => [ -            %StreamerSocket{transport_pid: 2}, -            %StreamerSocket{transport_pid: 1} -          ] -        } = Streamer.get_sockets() -      ) -    end - -    test "it will not add a duplicate socket", %{user: user} do -      Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) -      Streamer.add_socket("activity", %{transport_pid: 1, assigns: %{user: user}}) - -      assert( -        %{ -          "activity" => [ -            %StreamerSocket{transport_pid: 1} -          ] -        } = Streamer.get_sockets() -      ) -    end -  end -end diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 8b8d8af6c..ee530f4e9 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -12,13 +12,9 @@ defmodule Pleroma.Web.StreamerTest do    alias Pleroma.User    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.Streamer -  alias Pleroma.Web.Streamer.StreamerSocket -  alias Pleroma.Web.Streamer.Worker    @moduletag needs_streamer: true, capture_log: true -  @streamer_timeout 150 -  @streamer_start_wait 10    setup do: clear_config([:instance, :skip_thread_containment])    describe "user streams" do @@ -29,69 +25,35 @@ defmodule Pleroma.Web.StreamerTest do      end      test "it streams the user's post in the 'user' stream", %{user: user} do -      task = -        Task.async(fn -> -          assert_receive {:text, _}, @streamer_timeout -        end) - -      Streamer.add_socket( -        "user", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) - +      Streamer.add_socket("user", user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) - -      Streamer.stream("user", activity) -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^activity} +      refute Streamer.filtered_by_user?(user, activity)      end      test "it streams boosts of the user in the 'user' stream", %{user: user} do -      task = -        Task.async(fn -> -          assert_receive {:text, _}, @streamer_timeout -        end) - -      Streamer.add_socket( -        "user", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      Streamer.add_socket("user", user)        other_user = insert(:user)        {:ok, activity} = CommonAPI.post(other_user, %{"status" => "hey"})        {:ok, announce, _} = CommonAPI.repeat(activity.id, user) -      Streamer.stream("user", announce) -      Task.await(task) +      assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} +      refute Streamer.filtered_by_user?(user, announce)      end      test "it sends notify to in the 'user' stream", %{user: user, notify: notify} do -      task = -        Task.async(fn -> -          assert_receive {:text, _}, @streamer_timeout -        end) - -      Streamer.add_socket( -        "user", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) - +      Streamer.add_socket("user", user)        Streamer.stream("user", notify) -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^notify} +      refute Streamer.filtered_by_user?(user, notify)      end      test "it sends notify to in the 'user:notification' stream", %{user: user, notify: notify} do -      task = -        Task.async(fn -> -          assert_receive {:text, _}, @streamer_timeout -        end) - -      Streamer.add_socket( -        "user:notification", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) - +      Streamer.add_socket("user:notification", user)        Streamer.stream("user:notification", notify) -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^notify} +      refute Streamer.filtered_by_user?(user, notify)      end      test "it doesn't send notify to the 'user:notification' stream when a user is blocked", %{ @@ -100,18 +62,12 @@ defmodule Pleroma.Web.StreamerTest do        blocked = insert(:user)        {:ok, _user_relationship} = User.block(user, blocked) -      task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) - -      Streamer.add_socket( -        "user:notification", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      Streamer.add_socket("user:notification", user)        {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) -      {:ok, notif} = CommonAPI.favorite(blocked, activity.id) +      {:ok, _} = CommonAPI.favorite(blocked, activity.id) -      Streamer.stream("user:notification", notif) -      Task.await(task) +      refute_receive _      end      test "it doesn't send notify to the 'user:notification' stream when a thread is muted", %{ @@ -119,45 +75,50 @@ defmodule Pleroma.Web.StreamerTest do      } do        user2 = insert(:user) -      task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) +      {:ok, _} = CommonAPI.add_mute(user, activity) -      Streamer.add_socket( -        "user:notification", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      Streamer.add_socket("user:notification", user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) -      {:ok, activity} = CommonAPI.add_mute(user, activity) -      {:ok, notif} = CommonAPI.favorite(user2, activity.id) +      {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) -      Streamer.stream("user:notification", notif) -      Task.await(task) +      refute_receive _ +      assert Streamer.filtered_by_user?(user, favorite_activity)      end -    test "it doesn't send notify to the 'user:notification' stream' when a domain is blocked", %{ +    test "it sends favorite to 'user:notification' stream'", %{        user: user      } do        user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) -      task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) +      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) +      Streamer.add_socket("user:notification", user) +      {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) -      Streamer.add_socket( -        "user:notification", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      assert_receive {:render_with_user, _, "notification.json", notif} +      assert notif.activity.id == favorite_activity.id +      refute Streamer.filtered_by_user?(user, notif) +    end + +    test "it doesn't send the 'user:notification' stream' when a domain is blocked", %{ +      user: user +    } do +      user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})        {:ok, user} = User.block_domain(user, "hecking-lewd-place.com")        {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) -      {:ok, notif} = CommonAPI.favorite(user2, activity.id) +      Streamer.add_socket("user:notification", user) +      {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) -      Streamer.stream("user:notification", notif) -      Task.await(task) +      refute_receive _ +      assert Streamer.filtered_by_user?(user, favorite_activity)      end      test "it sends follow activities to the 'user:notification' stream", %{        user: user      } do        user_url = user.ap_id +      user2 = insert(:user)        body =          File.read!("test/fixtures/users_mock/localhost.json") @@ -169,79 +130,57 @@ defmodule Pleroma.Web.StreamerTest do            %Tesla.Env{status: 200, body: body}        end) -      user2 = insert(:user) -      task = Task.async(fn -> assert_receive {:text, _}, @streamer_timeout end) - -      Process.sleep(@streamer_start_wait) - -      Streamer.add_socket( -        "user:notification", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) - -      {:ok, _follower, _followed, _activity} = CommonAPI.follow(user2, user) +      Streamer.add_socket("user:notification", user) +      {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) -      # We don't directly pipe the notification to the streamer as it's already -      # generated as a side effect of CommonAPI.follow(). -      Task.await(task) +      assert_receive {:render_with_user, _, "notification.json", notif} +      assert notif.activity.id == follow_activity.id +      refute Streamer.filtered_by_user?(user, notif)      end    end -  test "it sends to public" do +  test "it sends to public authenticated" do      user = insert(:user)      other_user = insert(:user) -    task = -      Task.async(fn -> -        assert_receive {:text, _}, @streamer_timeout -      end) +    Streamer.add_socket("public", other_user) -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user -    } +    {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"}) +    assert_receive {:render_with_user, _, _, ^activity} +    refute Streamer.filtered_by_user?(user, activity) +  end +  test "works for deletions" do +    user = insert(:user) +    other_user = insert(:user)      {:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"}) -    topics = %{ -      "public" => [fake_socket] -    } - -    Worker.push_to_socket(topics, "public", activity) - -    Task.await(task) - -    task = -      Task.async(fn -> -        expected_event = -          %{ -            "event" => "delete", -            "payload" => activity.id -          } -          |> Jason.encode!() - -        assert_receive {:text, received_event}, @streamer_timeout -        assert received_event == expected_event -      end) +    Streamer.add_socket("public", user) -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user -    } +    {:ok, _} = CommonAPI.delete(activity.id, other_user) +    activity_id = activity.id +    assert_receive {:text, event} +    assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event) +  end -    {:ok, activity} = CommonAPI.delete(activity.id, other_user) +  test "it sends to public unauthenticated" do +    user = insert(:user) -    topics = %{ -      "public" => [fake_socket] -    } +    Streamer.add_socket("public", nil) -    Worker.push_to_socket(topics, "public", activity) +    {:ok, activity} = CommonAPI.post(user, %{"status" => "Test"}) +    activity_id = activity.id +    assert_receive {:text, event} +    assert %{"event" => "update", "payload" => payload} = Jason.decode!(event) +    assert %{"id" => ^activity_id} = Jason.decode!(payload) -    Task.await(task) +    {:ok, _} = CommonAPI.delete(activity.id, user) +    assert_receive {:text, event} +    assert %{"event" => "delete", "payload" => ^activity_id} = Jason.decode!(event)    end    describe "thread_containment" do -    test "it doesn't send to user if recipients invalid and thread containment is enabled" do +    test "it filters to user if recipients invalid and thread containment is enabled" do        Pleroma.Config.put([:instance, :skip_thread_containment], false)        author = insert(:user)        user = insert(:user) @@ -256,12 +195,10 @@ defmodule Pleroma.Web.StreamerTest do              )          ) -      task = Task.async(fn -> refute_receive {:text, _}, 1_000 end) -      fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} -      topics = %{"public" => [fake_socket]} -      Worker.push_to_socket(topics, "public", activity) - -      Task.await(task) +      Streamer.add_socket("public", user) +      Streamer.stream("public", activity) +      assert_receive {:render_with_user, _, _, ^activity} +      assert Streamer.filtered_by_user?(user, activity)      end      test "it sends message if recipients invalid and thread containment is disabled" do @@ -279,12 +216,11 @@ defmodule Pleroma.Web.StreamerTest do              )          ) -      task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) -      fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} -      topics = %{"public" => [fake_socket]} -      Worker.push_to_socket(topics, "public", activity) +      Streamer.add_socket("public", user) +      Streamer.stream("public", activity) -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^activity} +      refute Streamer.filtered_by_user?(user, activity)      end      test "it sends message if recipients invalid and thread containment is enabled but user's thread containment is disabled" do @@ -302,255 +238,168 @@ defmodule Pleroma.Web.StreamerTest do              )          ) -      task = Task.async(fn -> assert_receive {:text, _}, 1_000 end) -      fake_socket = %StreamerSocket{transport_pid: task.pid, user: user} -      topics = %{"public" => [fake_socket]} -      Worker.push_to_socket(topics, "public", activity) +      Streamer.add_socket("public", user) +      Streamer.stream("public", activity) -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^activity} +      refute Streamer.filtered_by_user?(user, activity)      end    end    describe "blocks" do -    test "it doesn't send messages involving blocked users" do +    test "it filters messages involving blocked users" do        user = insert(:user)        blocked_user = insert(:user)        {:ok, _user_relationship} = User.block(user, blocked_user) +      Streamer.add_socket("public", user)        {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) - -      task = -        Task.async(fn -> -          refute_receive {:text, _}, 1_000 -        end) - -      fake_socket = %StreamerSocket{ -        transport_pid: task.pid, -        user: user -      } - -      topics = %{ -        "public" => [fake_socket] -      } - -      Worker.push_to_socket(topics, "public", activity) - -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^activity} +      assert Streamer.filtered_by_user?(user, activity)      end -    test "it doesn't send messages transitively involving blocked users" do +    test "it filters messages transitively involving blocked users" do        blocker = insert(:user)        blockee = insert(:user)        friend = insert(:user) -      task = -        Task.async(fn -> -          refute_receive {:text, _}, 1_000 -        end) - -      fake_socket = %StreamerSocket{ -        transport_pid: task.pid, -        user: blocker -      } - -      topics = %{ -        "public" => [fake_socket] -      } +      Streamer.add_socket("public", blocker)        {:ok, _user_relationship} = User.block(blocker, blockee)        {:ok, activity_one} = CommonAPI.post(friend, %{"status" => "hey! @#{blockee.nickname}"}) -      Worker.push_to_socket(topics, "public", activity_one) +      assert_receive {:render_with_user, _, _, ^activity_one} +      assert Streamer.filtered_by_user?(blocker, activity_one)        {:ok, activity_two} = CommonAPI.post(blockee, %{"status" => "hey! @#{friend.nickname}"}) -      Worker.push_to_socket(topics, "public", activity_two) +      assert_receive {:render_with_user, _, _, ^activity_two} +      assert Streamer.filtered_by_user?(blocker, activity_two)        {:ok, activity_three} = CommonAPI.post(blockee, %{"status" => "hey! @#{blocker.nickname}"}) -      Worker.push_to_socket(topics, "public", activity_three) - -      Task.await(task) +      assert_receive {:render_with_user, _, _, ^activity_three} +      assert Streamer.filtered_by_user?(blocker, activity_three)      end    end -  test "it doesn't send unwanted DMs to list" do -    user_a = insert(:user) -    user_b = insert(:user) -    user_c = insert(:user) - -    {:ok, user_a} = User.follow(user_a, user_b) - -    {:ok, list} = List.create("Test", user_a) -    {:ok, list} = List.follow(list, user_b) - -    {:ok, activity} = -      CommonAPI.post(user_b, %{ -        "status" => "@#{user_c.nickname} Test", -        "visibility" => "direct" -      }) - -    task = -      Task.async(fn -> -        refute_receive {:text, _}, 1_000 -      end) - -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user_a -    } - -    topics = %{ -      "list:#{list.id}" => [fake_socket] -    } - -    Worker.handle_call({:stream, "list", activity}, self(), topics) - -    Task.await(task) -  end - -  test "it doesn't send unwanted private posts to list" do -    user_a = insert(:user) -    user_b = insert(:user) +  describe "lists" do +    test "it doesn't send unwanted DMs to list" do +      user_a = insert(:user) +      user_b = insert(:user) +      user_c = insert(:user) -    {:ok, list} = List.create("Test", user_a) -    {:ok, list} = List.follow(list, user_b) +      {:ok, user_a} = User.follow(user_a, user_b) -    {:ok, activity} = -      CommonAPI.post(user_b, %{ -        "status" => "Test", -        "visibility" => "private" -      }) +      {:ok, list} = List.create("Test", user_a) +      {:ok, list} = List.follow(list, user_b) -    task = -      Task.async(fn -> -        refute_receive {:text, _}, 1_000 -      end) +      Streamer.add_socket("list:#{list.id}", user_a) -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user_a -    } +      {:ok, _activity} = +        CommonAPI.post(user_b, %{ +          "status" => "@#{user_c.nickname} Test", +          "visibility" => "direct" +        }) -    topics = %{ -      "list:#{list.id}" => [fake_socket] -    } +      refute_receive _ +    end -    Worker.handle_call({:stream, "list", activity}, self(), topics) +    test "it doesn't send unwanted private posts to list" do +      user_a = insert(:user) +      user_b = insert(:user) -    Task.await(task) -  end +      {:ok, list} = List.create("Test", user_a) +      {:ok, list} = List.follow(list, user_b) -  test "it sends wanted private posts to list" do -    user_a = insert(:user) -    user_b = insert(:user) +      Streamer.add_socket("list:#{list.id}", user_a) -    {:ok, user_a} = User.follow(user_a, user_b) +      {:ok, _activity} = +        CommonAPI.post(user_b, %{ +          "status" => "Test", +          "visibility" => "private" +        }) -    {:ok, list} = List.create("Test", user_a) -    {:ok, list} = List.follow(list, user_b) +      refute_receive _ +    end -    {:ok, activity} = -      CommonAPI.post(user_b, %{ -        "status" => "Test", -        "visibility" => "private" -      }) +    test "it sends wanted private posts to list" do +      user_a = insert(:user) +      user_b = insert(:user) -    task = -      Task.async(fn -> -        assert_receive {:text, _}, 1_000 -      end) +      {:ok, user_a} = User.follow(user_a, user_b) -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user_a -    } +      {:ok, list} = List.create("Test", user_a) +      {:ok, list} = List.follow(list, user_b) -    Streamer.add_socket( -      "list:#{list.id}", -      fake_socket -    ) +      Streamer.add_socket("list:#{list.id}", user_a) -    Worker.handle_call({:stream, "list", activity}, self(), %{}) +      {:ok, activity} = +        CommonAPI.post(user_b, %{ +          "status" => "Test", +          "visibility" => "private" +        }) -    Task.await(task) +      assert_receive {:render_with_user, _, _, ^activity} +      refute Streamer.filtered_by_user?(user_a, activity) +    end    end -  test "it doesn't send muted reblogs" do -    user1 = insert(:user) -    user2 = insert(:user) -    user3 = insert(:user) -    CommonAPI.hide_reblogs(user1, user2) - -    {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) -    {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) - -    task = -      Task.async(fn -> -        refute_receive {:text, _}, 1_000 -      end) - -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user1 -    } - -    topics = %{ -      "public" => [fake_socket] -    } - -    Worker.push_to_socket(topics, "public", announce_activity) +  describe "muted reblogs" do +    test "it filters muted reblogs" do +      user1 = insert(:user) +      user2 = insert(:user) +      user3 = insert(:user) +      CommonAPI.follow(user1, user2) +      CommonAPI.hide_reblogs(user1, user2) -    Task.await(task) -  end +      {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) -  test "it does send non-reblog notification for reblog-muted actors" do -    user1 = insert(:user) -    user2 = insert(:user) -    user3 = insert(:user) -    CommonAPI.hide_reblogs(user1, user2) +      Streamer.add_socket("user", user1) +      {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) +      assert_receive {:render_with_user, _, _, ^announce_activity} +      assert Streamer.filtered_by_user?(user1, announce_activity) +    end -    {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) -    {:ok, favorite_activity} = CommonAPI.favorite(user2, create_activity.id) +    test "it filters reblog notification for reblog-muted actors" do +      user1 = insert(:user) +      user2 = insert(:user) +      CommonAPI.follow(user1, user2) +      CommonAPI.hide_reblogs(user1, user2) -    task = -      Task.async(fn -> -        assert_receive {:text, _}, 1_000 -      end) +      {:ok, create_activity} = CommonAPI.post(user1, %{"status" => "I'm kawen"}) +      Streamer.add_socket("user", user1) +      {:ok, _favorite_activity, _} = CommonAPI.repeat(create_activity.id, user2) -    fake_socket = %StreamerSocket{ -      transport_pid: task.pid, -      user: user1 -    } +      assert_receive {:render_with_user, _, "notification.json", notif} +      assert Streamer.filtered_by_user?(user1, notif) +    end -    topics = %{ -      "public" => [fake_socket] -    } +    test "it send non-reblog notification for reblog-muted actors" do +      user1 = insert(:user) +      user2 = insert(:user) +      CommonAPI.follow(user1, user2) +      CommonAPI.hide_reblogs(user1, user2) -    Worker.push_to_socket(topics, "public", favorite_activity) +      {:ok, create_activity} = CommonAPI.post(user1, %{"status" => "I'm kawen"}) +      Streamer.add_socket("user", user1) +      {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) -    Task.await(task) +      assert_receive {:render_with_user, _, "notification.json", notif} +      refute Streamer.filtered_by_user?(user1, notif) +    end    end -  test "it doesn't send posts from muted threads" do +  test "it filters posts from muted threads" do      user = insert(:user)      user2 = insert(:user) +    Streamer.add_socket("user", user2)      {:ok, user2, user, _activity} = CommonAPI.follow(user2, user) -      {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) - -    {:ok, activity} = CommonAPI.add_mute(user2, activity) - -    task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) - -    Streamer.add_socket( -      "user", -      %{transport_pid: task.pid, assigns: %{user: user2}} -    ) - -    Streamer.stream("user", activity) -    Task.await(task) +    {:ok, _} = CommonAPI.add_mute(user2, activity) +    assert_receive {:render_with_user, _, _, ^activity} +    assert Streamer.filtered_by_user?(user2, activity)    end    describe "direct streams" do @@ -562,22 +411,7 @@ defmodule Pleroma.Web.StreamerTest do        user = insert(:user)        another_user = insert(:user) -      task = -        Task.async(fn -> -          assert_receive {:text, received_event}, @streamer_timeout - -          assert %{"event" => "conversation", "payload" => received_payload} = -                   Jason.decode!(received_event) - -          assert %{"last_status" => last_status} = Jason.decode!(received_payload) -          [participation] = Participation.for_user(user) -          assert last_status["pleroma"]["direct_conversation_id"] == participation.id -        end) - -      Streamer.add_socket( -        "direct", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      Streamer.add_socket("direct", user)        {:ok, _create_activity} =          CommonAPI.post(another_user, %{ @@ -585,42 +419,47 @@ defmodule Pleroma.Web.StreamerTest do            "visibility" => "direct"          }) -      Task.await(task) +      assert_receive {:text, received_event} + +      assert %{"event" => "conversation", "payload" => received_payload} = +               Jason.decode!(received_event) + +      assert %{"last_status" => last_status} = Jason.decode!(received_payload) +      [participation] = Participation.for_user(user) +      assert last_status["pleroma"]["direct_conversation_id"] == participation.id      end      test "it doesn't send conversation update to the 'direct' stream when the last message in the conversation is deleted" do        user = insert(:user)        another_user = insert(:user) +      Streamer.add_socket("direct", user) +        {:ok, create_activity} =          CommonAPI.post(another_user, %{            "status" => "hi @#{user.nickname}",            "visibility" => "direct"          }) -      task = -        Task.async(fn -> -          assert_receive {:text, received_event}, @streamer_timeout -          assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) +      create_activity_id = create_activity.id +      assert_receive {:render_with_user, _, _, ^create_activity} +      assert_receive {:text, received_conversation1} +      assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) -          refute_receive {:text, _}, @streamer_timeout -        end) +      {:ok, _} = CommonAPI.delete(create_activity_id, another_user) -      Process.sleep(@streamer_start_wait) +      assert_receive {:text, received_event} -      Streamer.add_socket( -        "direct", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      assert %{"event" => "delete", "payload" => ^create_activity_id} = +               Jason.decode!(received_event) -      {:ok, _} = CommonAPI.delete(create_activity.id, another_user) - -      Task.await(task) +      refute_receive _      end      test "it sends conversation update to the 'direct' stream when a message is deleted" do        user = insert(:user)        another_user = insert(:user) +      Streamer.add_socket("direct", user)        {:ok, create_activity} =          CommonAPI.post(another_user, %{ @@ -630,35 +469,30 @@ defmodule Pleroma.Web.StreamerTest do        {:ok, create_activity2} =          CommonAPI.post(another_user, %{ -          "status" => "hi @#{user.nickname}", +          "status" => "hi @#{user.nickname} 2",            "in_reply_to_status_id" => create_activity.id,            "visibility" => "direct"          }) -      task = -        Task.async(fn -> -          assert_receive {:text, received_event}, @streamer_timeout -          assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) - -          assert_receive {:text, received_event}, @streamer_timeout +      assert_receive {:render_with_user, _, _, ^create_activity} +      assert_receive {:render_with_user, _, _, ^create_activity2} +      assert_receive {:text, received_conversation1} +      assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) +      assert_receive {:text, received_conversation1} +      assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) -          assert %{"event" => "conversation", "payload" => received_payload} = -                   Jason.decode!(received_event) - -          assert %{"last_status" => last_status} = Jason.decode!(received_payload) -          assert last_status["id"] == to_string(create_activity.id) -        end) +      {:ok, _} = CommonAPI.delete(create_activity2.id, another_user) -      Process.sleep(@streamer_start_wait) +      assert_receive {:text, received_event} +      assert %{"event" => "delete", "payload" => _} = Jason.decode!(received_event) -      Streamer.add_socket( -        "direct", -        %{transport_pid: task.pid, assigns: %{user: user}} -      ) +      assert_receive {:text, received_event} -      {:ok, _} = CommonAPI.delete(create_activity2.id, another_user) +      assert %{"event" => "conversation", "payload" => received_payload} = +               Jason.decode!(received_event) -      Task.await(task) +      assert %{"last_status" => last_status} = Jason.decode!(received_payload) +      assert last_status["id"] == to_string(create_activity.id)      end    end  end diff --git a/test/web/twitter_api/remote_follow_controller_test.exs b/test/web/twitter_api/remote_follow_controller_test.exs index 5ff8694a8..f7e54c26a 100644 --- a/test/web/twitter_api/remote_follow_controller_test.exs +++ b/test/web/twitter_api/remote_follow_controller_test.exs @@ -6,11 +6,14 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do    use Pleroma.Web.ConnCase    alias Pleroma.Config +  alias Pleroma.MFA +  alias Pleroma.MFA.TOTP    alias Pleroma.User    alias Pleroma.Web.CommonAPI    import ExUnit.CaptureLog    import Pleroma.Factory +  import Ecto.Query    setup do      Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -160,6 +163,119 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do      end    end +  describe "POST /ostatus_subscribe - follow/2 with enabled Two-Factor Auth " do +    test "render the MFA login form", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      user2 = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} +        }) +        |> response(200) + +      mfa_token = Pleroma.Repo.one(from(q in Pleroma.MFA.Token, where: q.user_id == ^user.id)) + +      assert response =~ "Two-factor authentication" +      assert response =~ "Authentication code" +      assert response =~ mfa_token.token +      refute user2.follower_address in User.following(user) +    end + +    test "returns error when password is incorrect", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      user2 = insert(:user) + +      response = +        conn +        |> post(remote_follow_path(conn, :do_follow), %{ +          "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id} +        }) +        |> response(200) + +      assert response =~ "Wrong username or password" +      refute user2.follower_address in User.following(user) +    end + +    test "follows", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      {:ok, %{token: token}} = MFA.Token.create_token(user) + +      user2 = insert(:user) +      otp_token = TOTP.generate_token(otp_secret) + +      conn = +        conn +        |> post( +          remote_follow_path(conn, :do_follow), +          %{ +            "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} +          } +        ) + +      assert redirected_to(conn) == "/users/#{user2.id}" +      assert user2.follower_address in User.following(user) +    end + +    test "returns error when auth code is incorrect", %{conn: conn} do +      otp_secret = TOTP.generate_secret() + +      user = +        insert(:user, +          multi_factor_authentication_settings: %MFA.Settings{ +            enabled: true, +            totp: %MFA.Settings.TOTP{secret: otp_secret, confirmed: true} +          } +        ) + +      {:ok, %{token: token}} = MFA.Token.create_token(user) + +      user2 = insert(:user) +      otp_token = TOTP.generate_token(TOTP.generate_secret()) + +      response = +        conn +        |> post( +          remote_follow_path(conn, :do_follow), +          %{ +            "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} +          } +        ) +        |> response(200) + +      assert response =~ "Wrong authentication code" +      refute user2.follower_address in User.following(user) +    end +  end +    describe "POST /ostatus_subscribe - follow/2 without assigned user " do      test "follows", %{conn: conn} do        user = insert(:user) diff --git a/test/web/web_finger/web_finger_test.exs b/test/web/web_finger/web_finger_test.exs index 4b4282727..f4884e0a2 100644 --- a/test/web/web_finger/web_finger_test.exs +++ b/test/web/web_finger/web_finger_test.exs @@ -67,7 +67,7 @@ defmodule Pleroma.Web.WebFingerTest do        assert data["magic_key"] == nil        assert data["salmon"] == nil -      assert data["topic"] == "https://mstdn.jp/users/kPherox.atom" +      assert data["topic"] == nil        assert data["subject"] == "acct:kPherox@mstdn.jp"        assert data["ap_id"] == "https://mstdn.jp/users/kPherox"        assert data["subscribe_address"] == "https://mstdn.jp/authorize_interaction?acct={uri}"  | 
