diff options
Diffstat (limited to 'test/web/admin_api/controllers')
8 files changed, 4785 insertions, 0 deletions
| diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs new file mode 100644 index 000000000..dbf478edf --- /dev/null +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -0,0 +1,1979 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do +  use Pleroma.Web.ConnCase +  use Oban.Testing, repo: Pleroma.Repo + +  import ExUnit.CaptureLog +  import Mock +  import Pleroma.Factory +  import Swoosh.TestAssertions + +  alias Pleroma.Activity +  alias Pleroma.Config +  alias Pleroma.HTML +  alias Pleroma.MFA +  alias Pleroma.ModerationLog +  alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers +  alias Pleroma.User +  alias Pleroma.Web +  alias Pleroma.Web.ActivityPub.Relay +  alias Pleroma.Web.CommonAPI +  alias Pleroma.Web.MediaProxy + +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + +    :ok +  end + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  test "with valid `admin_token` query parameter, skips OAuth scopes check" do +    clear_config([:admin_token], "password123") + +    user = insert(:user) + +    conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123") + +    assert json_response(conn, 200) +  end + +  describe "with [:auth, :enforce_oauth_admin_scope_usage]," do +    setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true) + +    test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope", +         %{admin: admin} do +      user = insert(:user) +      url = "/api/pleroma/admin/users/#{user.nickname}" + +      good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) +      good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) +      good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) + +      bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) +      bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) +      bad_token3 = nil + +      for good_token <- [good_token1, good_token2, good_token3] do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, 200) +      end + +      for good_token <- [good_token1, good_token2, good_token3] do +        conn = +          build_conn() +          |> assign(:user, nil) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end + +      for bad_token <- [bad_token1, bad_token2, bad_token3] do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, bad_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end +    end +  end + +  describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do +    setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false) + +    test "GET /api/pleroma/admin/users/:nickname requires " <> +           "read:accounts or admin:read:accounts or broader scope", +         %{admin: admin} do +      user = insert(:user) +      url = "/api/pleroma/admin/users/#{user.nickname}" + +      good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"]) +      good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"]) +      good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"]) +      good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"]) +      good_token5 = insert(:oauth_token, user: admin, scopes: ["read"]) + +      good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5] + +      bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"]) +      bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"]) +      bad_token3 = nil + +      for good_token <- good_tokens do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, 200) +      end + +      for good_token <- good_tokens do +        conn = +          build_conn() +          |> assign(:user, nil) +          |> assign(:token, good_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end + +      for bad_token <- [bad_token1, bad_token2, bad_token3] do +        conn = +          build_conn() +          |> assign(:user, admin) +          |> assign(:token, bad_token) +          |> get(url) + +        assert json_response(conn, :forbidden) +      end +    end +  end + +  describe "DELETE /api/pleroma/admin/users" do +    test "single user", %{admin: admin, conn: conn} do +      clear_config([:instance, :federating], true) + +      user = +        insert(:user, +          avatar: %{"url" => [%{"href" => "https://someurl"}]}, +          banner: %{"url" => [%{"href" => "https://somebanner"}]}, +          bio: "Hello world!", +          name: "A guy" +        ) + +      # Create some activities to check they got deleted later +      follower = insert(:user) +      {:ok, _} = CommonAPI.post(user, %{status: "test"}) +      {:ok, _, _, _} = CommonAPI.follow(user, follower) +      {:ok, _, _, _} = CommonAPI.follow(follower, user) +      user = Repo.get(User, user.id) +      assert user.note_count == 1 +      assert user.follower_count == 1 +      assert user.following_count == 1 +      refute user.deactivated + +      with_mock Pleroma.Web.Federator, +        publish: fn _ -> nil end, +        perform: fn _, _ -> nil end do +        conn = +          conn +          |> put_req_header("accept", "application/json") +          |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") + +        ObanHelpers.perform_all() + +        assert User.get_by_nickname(user.nickname).deactivated + +        log_entry = Repo.one(ModerationLog) + +        assert ModerationLog.get_log_entry_message(log_entry) == +                 "@#{admin.nickname} deleted users: @#{user.nickname}" + +        assert json_response(conn, 200) == [user.nickname] + +        user = Repo.get(User, user.id) +        assert user.deactivated + +        assert user.avatar == %{} +        assert user.banner == %{} +        assert user.note_count == 0 +        assert user.follower_count == 0 +        assert user.following_count == 0 +        assert user.bio == nil +        assert user.name == nil + +        assert called(Pleroma.Web.Federator.publish(:_)) +      end +    end + +    test "multiple users", %{admin: admin, conn: conn} do +      user_one = insert(:user) +      user_two = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> delete("/api/pleroma/admin/users", %{ +          nicknames: [user_one.nickname, user_two.nickname] +        }) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}" + +      response = json_response(conn, 200) +      assert response -- [user_one.nickname, user_two.nickname] == [] +    end +  end + +  describe "/api/pleroma/admin/users" do +    test "Create", %{conn: conn} do +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => "lain", +              "email" => "lain@example.org", +              "password" => "test" +            }, +            %{ +              "nickname" => "lain2", +              "email" => "lain2@example.org", +              "password" => "test" +            } +          ] +        }) + +      response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type")) +      assert response == ["success", "success"] + +      log_entry = Repo.one(ModerationLog) + +      assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == [] +    end + +    test "Cannot create user with existing email", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => "lain", +              "email" => user.email, +              "password" => "test" +            } +          ] +        }) + +      assert json_response(conn, 409) == [ +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => user.email, +                   "nickname" => "lain" +                 }, +                 "error" => "email has already been taken", +                 "type" => "error" +               } +             ] +    end + +    test "Cannot create user with existing nickname", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => user.nickname, +              "email" => "someuser@plerama.social", +              "password" => "test" +            } +          ] +        }) + +      assert json_response(conn, 409) == [ +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => "someuser@plerama.social", +                   "nickname" => user.nickname +                 }, +                 "error" => "nickname has already been taken", +                 "type" => "error" +               } +             ] +    end + +    test "Multiple user creation works in transaction", %{conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users", %{ +          "users" => [ +            %{ +              "nickname" => "newuser", +              "email" => "newuser@pleroma.social", +              "password" => "test" +            }, +            %{ +              "nickname" => "lain", +              "email" => user.email, +              "password" => "test" +            } +          ] +        }) + +      assert json_response(conn, 409) == [ +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => user.email, +                   "nickname" => "lain" +                 }, +                 "error" => "email has already been taken", +                 "type" => "error" +               }, +               %{ +                 "code" => 409, +                 "data" => %{ +                   "email" => "newuser@pleroma.social", +                   "nickname" => "newuser" +                 }, +                 "error" => "", +                 "type" => "error" +               } +             ] + +      assert User.get_by_nickname("newuser") === nil +    end +  end + +  describe "/api/pleroma/admin/users/:nickname" do +    test "Show", %{conn: conn} do +      user = insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") + +      expected = %{ +        "deactivated" => false, +        "id" => to_string(user.id), +        "local" => true, +        "nickname" => user.nickname, +        "roles" => %{"admin" => false, "moderator" => false}, +        "tags" => [], +        "avatar" => User.avatar_url(user) |> MediaProxy.url(), +        "display_name" => HTML.strip_tags(user.name || user.nickname), +        "confirmation_pending" => false, +        "approval_pending" => false, +        "url" => user.ap_id, +        "registration_reason" => nil, +        "actor_type" => "Person" +      } + +      assert expected == json_response(conn, 200) +    end + +    test "when the user doesn't exist", %{conn: conn} do +      user = build(:user) + +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}") + +      assert %{"error" => "Not found"} == json_response(conn, 404) +    end +  end + +  describe "/api/pleroma/admin/users/follow" do +    test "allows to force-follow another user", %{admin: admin, conn: conn} do +      user = insert(:user) +      follower = insert(:user) + +      conn +      |> put_req_header("accept", "application/json") +      |> post("/api/pleroma/admin/users/follow", %{ +        "follower" => follower.nickname, +        "followed" => user.nickname +      }) + +      user = User.get_cached_by_id(user.id) +      follower = User.get_cached_by_id(follower.id) + +      assert User.following?(follower, user) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}" +    end +  end + +  describe "/api/pleroma/admin/users/unfollow" do +    test "allows to force-unfollow another user", %{admin: admin, conn: conn} do +      user = insert(:user) +      follower = insert(:user) + +      User.follow(follower, user) + +      conn +      |> put_req_header("accept", "application/json") +      |> post("/api/pleroma/admin/users/unfollow", %{ +        "follower" => follower.nickname, +        "followed" => user.nickname +      }) + +      user = User.get_cached_by_id(user.id) +      follower = User.get_cached_by_id(follower.id) + +      refute User.following?(follower, user) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}" +    end +  end + +  describe "PUT /api/pleroma/admin/users/tag" do +    setup %{conn: conn} do +      user1 = insert(:user, %{tags: ["x"]}) +      user2 = insert(:user, %{tags: ["y"]}) +      user3 = insert(:user, %{tags: ["unchanged"]}) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> put( +          "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> +            "#{user2.nickname}&tags[]=foo&tags[]=bar" +        ) + +      %{conn: conn, user1: user1, user2: user2, user3: user3} +    end + +    test "it appends specified tags to users with specified nicknames", %{ +      conn: conn, +      admin: admin, +      user1: user1, +      user2: user2 +    } do +      assert empty_json_response(conn) +      assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"] +      assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"] + +      log_entry = Repo.one(ModerationLog) + +      users = +        [user1.nickname, user2.nickname] +        |> Enum.map(&"@#{&1}") +        |> Enum.join(", ") + +      tags = ["foo", "bar"] |> Enum.join(", ") + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} added tags: #{tags} to users: #{users}" +    end + +    test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do +      assert empty_json_response(conn) +      assert User.get_cached_by_id(user3.id).tags == ["unchanged"] +    end +  end + +  describe "DELETE /api/pleroma/admin/users/tag" do +    setup %{conn: conn} do +      user1 = insert(:user, %{tags: ["x"]}) +      user2 = insert(:user, %{tags: ["y", "z"]}) +      user3 = insert(:user, %{tags: ["unchanged"]}) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> delete( +          "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> +            "#{user2.nickname}&tags[]=x&tags[]=z" +        ) + +      %{conn: conn, user1: user1, user2: user2, user3: user3} +    end + +    test "it removes specified tags from users with specified nicknames", %{ +      conn: conn, +      admin: admin, +      user1: user1, +      user2: user2 +    } do +      assert empty_json_response(conn) +      assert User.get_cached_by_id(user1.id).tags == [] +      assert User.get_cached_by_id(user2.id).tags == ["y"] + +      log_entry = Repo.one(ModerationLog) + +      users = +        [user1.nickname, user2.nickname] +        |> Enum.map(&"@#{&1}") +        |> Enum.join(", ") + +      tags = ["x", "z"] |> Enum.join(", ") + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} removed tags: #{tags} from users: #{users}" +    end + +    test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do +      assert empty_json_response(conn) +      assert User.get_cached_by_id(user3.id).tags == ["unchanged"] +    end +  end + +  describe "/api/pleroma/admin/users/:nickname/permission_group" do +    test "GET is giving user_info", %{admin: admin, conn: conn} do +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/") + +      assert json_response(conn, 200) == %{ +               "is_admin" => true, +               "is_moderator" => false +             } +    end + +    test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do +      user = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") + +      assert json_response(conn, 200) == %{ +               "is_admin" => true +             } + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{user.nickname} admin" +    end + +    test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do +      user_one = insert(:user) +      user_two = insert(:user) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> post("/api/pleroma/admin/users/permission_group/admin", %{ +          nicknames: [user_one.nickname, user_two.nickname] +        }) + +      assert json_response(conn, 200) == %{"is_admin" => true} + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin" +    end + +    test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do +      user = insert(:user, is_admin: true) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin") + +      assert json_response(conn, 200) == %{"is_admin" => false} + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} revoked admin role from @#{user.nickname}" +    end + +    test "/:right DELETE, can remove from a permission group (multiple)", %{ +      admin: admin, +      conn: conn +    } do +      user_one = insert(:user, is_admin: true) +      user_two = insert(:user, is_admin: true) + +      conn = +        conn +        |> put_req_header("accept", "application/json") +        |> delete("/api/pleroma/admin/users/permission_group/admin", %{ +          nicknames: [user_one.nickname, user_two.nickname] +        }) + +      assert json_response(conn, 200) == %{"is_admin" => false} + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{ +                 user_two.nickname +               }" +    end +  end + +  test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do +    user = insert(:user) + +    conn = +      conn +      |> put_req_header("accept", "application/json") +      |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset") + +    resp = json_response(conn, 200) + +    assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"]) +  end + +  describe "GET /api/pleroma/admin/users" do +    test "renders users array for the first page", %{conn: conn, admin: admin} do +      user = insert(:user, local: false, tags: ["foo", "bar"]) +      user2 = insert(:user, approval_pending: true, registration_reason: "I'm a chill dude") + +      conn = get(conn, "/api/pleroma/admin/users?page=1") + +      users = +        [ +          %{ +            "deactivated" => admin.deactivated, +            "id" => admin.id, +            "nickname" => admin.nickname, +            "roles" => %{"admin" => true, "moderator" => false}, +            "local" => true, +            "tags" => [], +            "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(admin.name || admin.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => admin.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          }, +          %{ +            "deactivated" => user.deactivated, +            "id" => user.id, +            "nickname" => user.nickname, +            "roles" => %{"admin" => false, "moderator" => false}, +            "local" => false, +            "tags" => ["foo", "bar"], +            "avatar" => User.avatar_url(user) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user.name || user.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => user.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          }, +          %{ +            "deactivated" => user2.deactivated, +            "id" => user2.id, +            "nickname" => user2.nickname, +            "roles" => %{"admin" => false, "moderator" => false}, +            "local" => true, +            "tags" => [], +            "avatar" => User.avatar_url(user2) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user2.name || user2.nickname), +            "confirmation_pending" => false, +            "approval_pending" => true, +            "url" => user2.ap_id, +            "registration_reason" => "I'm a chill dude", +            "actor_type" => "Person" +          } +        ] +        |> Enum.sort_by(& &1["nickname"]) + +      assert json_response(conn, 200) == %{ +               "count" => 3, +               "page_size" => 50, +               "users" => users +             } +    end + +    test "pagination works correctly with service users", %{conn: conn} do +      service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido") + +      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 %{"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 %{"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 +    end + +    test "renders empty array for the second page", %{conn: conn} do +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?page=2") + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 50, +               "users" => [] +             } +    end + +    test "regular search", %{conn: conn} do +      user = insert(:user, nickname: "bob") + +      conn = get(conn, "/api/pleroma/admin/users?query=bo") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "search by domain", %{conn: conn} do +      user = insert(:user, nickname: "nickname@domain.com") +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?query=domain.com") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "search by full nickname", %{conn: conn} do +      user = insert(:user, nickname: "nickname@domain.com") +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "search by display name", %{conn: conn} do +      user = insert(:user, name: "Display name") +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?name=display") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "search by email", %{conn: conn} do +      user = insert(:user, email: "email@example.com") +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?email=email@example.com") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "regular search with page size", %{conn: conn} do +      user = insert(:user, nickname: "aalice") +      user2 = insert(:user, nickname: "alice") + +      conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1") + +      assert json_response(conn1, 200) == %{ +               "count" => 2, +               "page_size" => 1, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } + +      conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2") + +      assert json_response(conn2, 200) == %{ +               "count" => 2, +               "page_size" => 1, +               "users" => [ +                 %{ +                   "deactivated" => user2.deactivated, +                   "id" => user2.id, +                   "nickname" => user2.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user2) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user2.name || user2.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user2.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "only local users" do +      admin = insert(:user, is_admin: true, nickname: "john") +      token = insert(:oauth_admin_token, user: admin) +      user = insert(:user, nickname: "bob") + +      insert(:user, nickname: "bobb", local: false) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> assign(:token, token) +        |> get("/api/pleroma/admin/users?query=bo&filters=local") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "only local users with no query", %{conn: conn, admin: old_admin} do +      admin = insert(:user, is_admin: true, nickname: "john") +      user = insert(:user, nickname: "bob") + +      insert(:user, nickname: "bobb", local: false) + +      conn = get(conn, "/api/pleroma/admin/users?filters=local") + +      users = +        [ +          %{ +            "deactivated" => user.deactivated, +            "id" => user.id, +            "nickname" => user.nickname, +            "roles" => %{"admin" => false, "moderator" => false}, +            "local" => true, +            "tags" => [], +            "avatar" => User.avatar_url(user) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user.name || user.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => user.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          }, +          %{ +            "deactivated" => admin.deactivated, +            "id" => admin.id, +            "nickname" => admin.nickname, +            "roles" => %{"admin" => true, "moderator" => false}, +            "local" => true, +            "tags" => [], +            "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(admin.name || admin.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => admin.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          }, +          %{ +            "deactivated" => false, +            "id" => old_admin.id, +            "local" => true, +            "nickname" => old_admin.nickname, +            "roles" => %{"admin" => true, "moderator" => false}, +            "tags" => [], +            "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => old_admin.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          } +        ] +        |> Enum.sort_by(& &1["nickname"]) + +      assert json_response(conn, 200) == %{ +               "count" => 3, +               "page_size" => 50, +               "users" => users +             } +    end + +    test "only unapproved users", %{conn: conn} do +      user = +        insert(:user, +          nickname: "sadboy", +          approval_pending: true, +          registration_reason: "Plz let me in!" +        ) + +      insert(:user, nickname: "happyboy", approval_pending: false) + +      conn = get(conn, "/api/pleroma/admin/users?filters=need_approval") + +      users = +        [ +          %{ +            "deactivated" => user.deactivated, +            "id" => user.id, +            "nickname" => user.nickname, +            "roles" => %{"admin" => false, "moderator" => false}, +            "local" => true, +            "tags" => [], +            "avatar" => User.avatar_url(user) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user.name || user.nickname), +            "confirmation_pending" => false, +            "approval_pending" => true, +            "url" => user.ap_id, +            "registration_reason" => "Plz let me in!", +            "actor_type" => "Person" +          } +        ] +        |> Enum.sort_by(& &1["nickname"]) + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => users +             } +    end + +    test "load only admins", %{conn: conn, admin: admin} do +      second_admin = insert(:user, is_admin: true) +      insert(:user) +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?filters=is_admin") + +      users = +        [ +          %{ +            "deactivated" => false, +            "id" => admin.id, +            "nickname" => admin.nickname, +            "roles" => %{"admin" => true, "moderator" => false}, +            "local" => admin.local, +            "tags" => [], +            "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(admin.name || admin.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => admin.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          }, +          %{ +            "deactivated" => false, +            "id" => second_admin.id, +            "nickname" => second_admin.nickname, +            "roles" => %{"admin" => true, "moderator" => false}, +            "local" => second_admin.local, +            "tags" => [], +            "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => second_admin.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          } +        ] +        |> Enum.sort_by(& &1["nickname"]) + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 50, +               "users" => users +             } +    end + +    test "load only moderators", %{conn: conn} do +      moderator = insert(:user, is_moderator: true) +      insert(:user) +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => false, +                   "id" => moderator.id, +                   "nickname" => moderator.nickname, +                   "roles" => %{"admin" => false, "moderator" => true}, +                   "local" => moderator.local, +                   "tags" => [], +                   "avatar" => User.avatar_url(moderator) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(moderator.name || moderator.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => moderator.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "load users with tags list", %{conn: conn} do +      user1 = insert(:user, tags: ["first"]) +      user2 = insert(:user, tags: ["second"]) +      insert(:user) +      insert(:user) + +      conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second") + +      users = +        [ +          %{ +            "deactivated" => false, +            "id" => user1.id, +            "nickname" => user1.nickname, +            "roles" => %{"admin" => false, "moderator" => false}, +            "local" => user1.local, +            "tags" => ["first"], +            "avatar" => User.avatar_url(user1) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user1.name || user1.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => user1.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          }, +          %{ +            "deactivated" => false, +            "id" => user2.id, +            "nickname" => user2.nickname, +            "roles" => %{"admin" => false, "moderator" => false}, +            "local" => user2.local, +            "tags" => ["second"], +            "avatar" => User.avatar_url(user2) |> MediaProxy.url(), +            "display_name" => HTML.strip_tags(user2.name || user2.nickname), +            "confirmation_pending" => false, +            "approval_pending" => false, +            "url" => user2.ap_id, +            "registration_reason" => nil, +            "actor_type" => "Person" +          } +        ] +        |> Enum.sort_by(& &1["nickname"]) + +      assert json_response(conn, 200) == %{ +               "count" => 2, +               "page_size" => 50, +               "users" => users +             } +    end + +    test "`active` filters out users pending approval", %{token: token} do +      insert(:user, approval_pending: true) +      %{id: user_id} = insert(:user, approval_pending: false) +      %{id: admin_id} = token.user + +      conn = +        build_conn() +        |> assign(:user, token.user) +        |> assign(:token, token) +        |> get("/api/pleroma/admin/users?filters=active") + +      assert %{ +               "count" => 2, +               "page_size" => 50, +               "users" => [ +                 %{"id" => ^admin_id}, +                 %{"id" => ^user_id} +               ] +             } = json_response(conn, 200) +    end + +    test "it works with multiple filters" do +      admin = insert(:user, nickname: "john", is_admin: true) +      token = insert(:oauth_admin_token, user: admin) +      user = insert(:user, nickname: "bob", local: false, deactivated: true) + +      insert(:user, nickname: "ken", local: true, deactivated: true) +      insert(:user, nickname: "bobb", local: false, deactivated: false) + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> assign(:token, token) +        |> get("/api/pleroma/admin/users?filters=deactivated,external") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => user.deactivated, +                   "id" => user.id, +                   "nickname" => user.nickname, +                   "roles" => %{"admin" => false, "moderator" => false}, +                   "local" => user.local, +                   "tags" => [], +                   "avatar" => User.avatar_url(user) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(user.name || user.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => user.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end + +    test "it omits relay user", %{admin: admin, conn: conn} do +      assert %User{} = Relay.get_actor() + +      conn = get(conn, "/api/pleroma/admin/users") + +      assert json_response(conn, 200) == %{ +               "count" => 1, +               "page_size" => 50, +               "users" => [ +                 %{ +                   "deactivated" => admin.deactivated, +                   "id" => admin.id, +                   "nickname" => admin.nickname, +                   "roles" => %{"admin" => true, "moderator" => false}, +                   "local" => true, +                   "tags" => [], +                   "avatar" => User.avatar_url(admin) |> MediaProxy.url(), +                   "display_name" => HTML.strip_tags(admin.name || admin.nickname), +                   "confirmation_pending" => false, +                   "approval_pending" => false, +                   "url" => admin.ap_id, +                   "registration_reason" => nil, +                   "actor_type" => "Person" +                 } +               ] +             } +    end +  end + +  test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do +    user_one = insert(:user, deactivated: true) +    user_two = insert(:user, deactivated: true) + +    conn = +      patch( +        conn, +        "/api/pleroma/admin/users/activate", +        %{nicknames: [user_one.nickname, user_two.nickname]} +      ) + +    response = json_response(conn, 200) +    assert Enum.map(response["users"], & &1["deactivated"]) == [false, false] + +    log_entry = Repo.one(ModerationLog) + +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}" +  end + +  test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do +    user_one = insert(:user, deactivated: false) +    user_two = insert(:user, deactivated: false) + +    conn = +      patch( +        conn, +        "/api/pleroma/admin/users/deactivate", +        %{nicknames: [user_one.nickname, user_two.nickname]} +      ) + +    response = json_response(conn, 200) +    assert Enum.map(response["users"], & &1["deactivated"]) == [true, true] + +    log_entry = Repo.one(ModerationLog) + +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}" +  end + +  test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do +    user_one = insert(:user, approval_pending: true) +    user_two = insert(:user, approval_pending: true) + +    conn = +      patch( +        conn, +        "/api/pleroma/admin/users/approve", +        %{nicknames: [user_one.nickname, user_two.nickname]} +      ) + +    response = json_response(conn, 200) +    assert Enum.map(response["users"], & &1["approval_pending"]) == [false, false] + +    log_entry = Repo.one(ModerationLog) + +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}" +  end + +  test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do +    user = insert(:user) + +    conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation") + +    assert json_response(conn, 200) == +             %{ +               "deactivated" => !user.deactivated, +               "id" => user.id, +               "nickname" => user.nickname, +               "roles" => %{"admin" => false, "moderator" => false}, +               "local" => true, +               "tags" => [], +               "avatar" => User.avatar_url(user) |> MediaProxy.url(), +               "display_name" => HTML.strip_tags(user.name || user.nickname), +               "confirmation_pending" => false, +               "approval_pending" => false, +               "url" => user.ap_id, +               "registration_reason" => nil, +               "actor_type" => "Person" +             } + +    log_entry = Repo.one(ModerationLog) + +    assert ModerationLog.get_log_entry_message(log_entry) == +             "@#{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 == %{"error" => "Not found"} +    end +  end + +  describe "GET /api/pleroma/admin/restart" do +    setup do: clear_config(:configurable_from_database, true) + +    test "pleroma restarts", %{conn: conn} do +      capture_log(fn -> +        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{} +      end) =~ "pleroma restarted" + +      refute Restarter.Pleroma.need_reboot?() +    end +  end + +  test "need_reboot flag", %{conn: conn} do +    assert conn +           |> get("/api/pleroma/admin/need_reboot") +           |> json_response(200) == %{"need_reboot" => false} + +    Restarter.Pleroma.need_reboot() + +    assert conn +           |> get("/api/pleroma/admin/need_reboot") +           |> json_response(200) == %{"need_reboot" => true} + +    on_exit(fn -> Restarter.Pleroma.refresh() end) +  end + +  describe "GET /api/pleroma/admin/users/:nickname/statuses" do +    setup do +      user = insert(:user) + +      date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!() +      date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!() +      date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!() + +      insert(:note_activity, user: user, published: date1) +      insert(:note_activity, user: user, published: date2) +      insert(:note_activity, user: user, published: date3) + +      %{user: user} +    end + +    test "renders user's statuses", %{conn: conn, user: user} do +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + +      assert json_response(conn, 200) |> length() == 3 +    end + +    test "renders user's statuses with a limit", %{conn: conn, user: user} do +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2") + +      assert json_response(conn, 200) |> length() == 2 +    end + +    test "doesn't return private statuses by default", %{conn: conn, user: user} do +      {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"}) + +      {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"}) + +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses") + +      assert json_response(conn, 200) |> length() == 4 +    end + +    test "returns private statuses with godmode on", %{conn: conn, user: user} do +      {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"}) + +      {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"}) + +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true") + +      assert json_response(conn, 200) |> length() == 5 +    end + +    test "excludes reblogs by default", %{conn: conn, user: user} do +      other_user = insert(:user) +      {:ok, activity} = CommonAPI.post(user, %{status: "."}) +      {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user) + +      conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses") +      assert json_response(conn_res, 200) |> length() == 0 + +      conn_res = +        get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true") + +      assert json_response(conn_res, 200) |> length() == 1 +    end +  end + +  describe "GET /api/pleroma/admin/moderation_log" do +    setup do +      moderator = insert(:user, is_moderator: true) + +      %{moderator: moderator} +    end + +    test "returns the log", %{conn: conn, admin: admin} do +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second) +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second) +      }) + +      conn = get(conn, "/api/pleroma/admin/moderation_log") + +      response = json_response(conn, 200) +      [first_entry, second_entry] = response["items"] + +      assert response["total"] == 2 +      assert first_entry["data"]["action"] == "relay_unfollow" + +      assert first_entry["message"] == +               "@#{admin.nickname} unfollowed relay: https://example.org/relay" + +      assert second_entry["data"]["action"] == "relay_follow" + +      assert second_entry["message"] == +               "@#{admin.nickname} followed relay: https://example.org/relay" +    end + +    test "returns the log with pagination", %{conn: conn, admin: admin} do +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second) +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second) +      }) + +      conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1") + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 2 +      assert response1["items"] |> length() == 1 +      assert first_entry["data"]["action"] == "relay_unfollow" + +      assert first_entry["message"] == +               "@#{admin.nickname} unfollowed relay: https://example.org/relay" + +      conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2") + +      response2 = json_response(conn2, 200) +      [second_entry] = response2["items"] + +      assert response2["total"] == 2 +      assert response2["items"] |> length() == 1 +      assert second_entry["data"]["action"] == "relay_follow" + +      assert second_entry["message"] == +               "@#{admin.nickname} followed relay: https://example.org/relay" +    end + +    test "filters log by date", %{conn: conn, admin: admin} do +      first_date = "2017-08-15T15:47:06Z" +      second_date = "2017-08-20T15:47:06Z" + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.from_iso8601!(first_date) +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        }, +        inserted_at: NaiveDateTime.from_iso8601!(second_date) +      }) + +      conn1 = +        get( +          conn, +          "/api/pleroma/admin/moderation_log?start_date=#{second_date}" +        ) + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 1 +      assert first_entry["data"]["action"] == "relay_unfollow" + +      assert first_entry["message"] == +               "@#{admin.nickname} unfollowed relay: https://example.org/relay" +    end + +    test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => admin.id, +            "nickname" => admin.nickname, +            "type" => "user" +          }, +          action: "relay_follow", +          target: "https://example.org/relay" +        } +      }) + +      Repo.insert(%ModerationLog{ +        data: %{ +          actor: %{ +            "id" => moderator.id, +            "nickname" => moderator.nickname, +            "type" => "user" +          }, +          action: "relay_unfollow", +          target: "https://example.org/relay" +        } +      }) + +      conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}") + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 1 +      assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id +    end + +    test "returns log filtered by search", %{conn: conn, moderator: moderator} do +      ModerationLog.insert_log(%{ +        actor: moderator, +        action: "relay_follow", +        target: "https://example.org/relay" +      }) + +      ModerationLog.insert_log(%{ +        actor: moderator, +        action: "relay_unfollow", +        target: "https://example.org/relay" +      }) + +      conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo") + +      response1 = json_response(conn1, 200) +      [first_entry] = response1["items"] + +      assert response1["total"] == 1 + +      assert get_in(first_entry, ["data", "message"]) == +               "@#{moderator.nickname} unfollowed relay: https://example.org/relay" +    end +  end + +  test "gets a remote users when [:instance, :limit_to_local_content] is set to :unauthenticated", +       %{conn: conn} do +    clear_config(Pleroma.Config.get([:instance, :limit_to_local_content]), :unauthenticated) +    user = insert(:user, %{local: false, nickname: "u@peer1.com"}) +    conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials") + +    assert json_response(conn, 200) +  end + +  describe "GET /users/:nickname/credentials" do +    test "gets the user credentials", %{conn: conn} do +      user = insert(:user) +      conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials") + +      response = assert json_response(conn, 200) +      assert response["email"] == user.email +    end + +    test "returns 403 if requested by a non-admin" do +      user = insert(:user) + +      conn = +        build_conn() +        |> assign(:user, user) +        |> get("/api/pleroma/admin/users/#{user.nickname}/credentials") + +      assert json_response(conn, :forbidden) +    end +  end + +  describe "PATCH /users/:nickname/credentials" do +    setup do +      user = insert(:user) +      [user: user] +    end + +    test "changes password and email", %{conn: conn, admin: admin, user: user} do +      assert user.password_reset_pending == false + +      conn = +        patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{ +          "password" => "new_password", +          "email" => "new_email@example.com", +          "name" => "new_name" +        }) + +      assert json_response(conn, 200) == %{"status" => "success"} + +      ObanHelpers.perform_all() + +      updated_user = User.get_by_id(user.id) + +      assert updated_user.email == "new_email@example.com" +      assert updated_user.name == "new_name" +      assert updated_user.password_hash != user.password_hash +      assert updated_user.password_reset_pending == true + +      [log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort() + +      assert ModerationLog.get_log_entry_message(log_entry1) == +               "@#{admin.nickname} updated users: @#{user.nickname}" + +      assert ModerationLog.get_log_entry_message(log_entry2) == +               "@#{admin.nickname} forced password reset for users: @#{user.nickname}" +    end + +    test "returns 403 if requested by a non-admin", %{user: user} do +      conn = +        build_conn() +        |> assign(:user, user) +        |> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{ +          "password" => "new_password", +          "email" => "new_email@example.com", +          "name" => "new_name" +        }) + +      assert json_response(conn, :forbidden) +    end + +    test "changes actor type from permitted list", %{conn: conn, user: user} do +      assert user.actor_type == "Person" + +      assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{ +               "actor_type" => "Service" +             }) +             |> json_response(200) == %{"status" => "success"} + +      updated_user = User.get_by_id(user.id) + +      assert updated_user.actor_type == "Service" + +      assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{ +               "actor_type" => "Application" +             }) +             |> json_response(400) == %{"errors" => %{"actor_type" => "is invalid"}} +    end + +    test "update non existing user", %{conn: conn} do +      assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{ +               "password" => "new_password" +             }) +             |> json_response(404) == %{"error" => "Not found"} +    end +  end + +  describe "PATCH /users/:nickname/force_password_reset" do +    test "sets password_reset_pending to true", %{conn: conn} do +      user = insert(:user) +      assert user.password_reset_pending == false + +      conn = +        patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]}) + +      assert empty_json_response(conn) == "" + +      ObanHelpers.perform_all() + +      assert User.get_by_id(user.id).password_reset_pending == true +    end +  end + +  describe "instances" do +    test "GET /instances/:instance/statuses", %{conn: conn} do +      user = insert(:user, local: false, nickname: "archaeme@archae.me") +      user2 = insert(:user, local: false, nickname: "test@test.com") +      insert_pair(:note_activity, user: user) +      activity = insert(:note_activity, user: user2) + +      ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") + +      response = json_response(ret_conn, 200) + +      assert length(response) == 2 + +      ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses") + +      response = json_response(ret_conn, 200) + +      assert length(response) == 1 + +      ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses") + +      response = json_response(ret_conn, 200) + +      assert Enum.empty?(response) + +      CommonAPI.repeat(activity.id, user) + +      ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses") +      response = json_response(ret_conn, 200) +      assert length(response) == 2 + +      ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true") +      response = json_response(ret_conn, 200) +      assert length(response) == 3 +    end +  end + +  describe "PATCH /confirm_email" do +    test "it confirms emails of two users", %{conn: conn, admin: admin} do +      [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + +      assert first_user.confirmation_pending == true +      assert second_user.confirmation_pending == true + +      ret_conn = +        patch(conn, "/api/pleroma/admin/users/confirm_email", %{ +          nicknames: [ +            first_user.nickname, +            second_user.nickname +          ] +        }) + +      assert ret_conn.status == 200 + +      assert first_user.confirmation_pending == true +      assert second_user.confirmation_pending == true + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{ +                 second_user.nickname +               }" +    end +  end + +  describe "PATCH /resend_confirmation_email" do +    test "it resend emails for two users", %{conn: conn, admin: admin} do +      [first_user, second_user] = insert_pair(:user, confirmation_pending: true) + +      ret_conn = +        patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{ +          nicknames: [ +            first_user.nickname, +            second_user.nickname +          ] +        }) + +      assert ret_conn.status == 200 + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{ +                 second_user.nickname +               }" + +      ObanHelpers.perform_all() +      assert_email_sent(Pleroma.Emails.UserEmail.account_confirmation_email(first_user)) +    end +  end + +  describe "/api/pleroma/admin/stats" do +    test "status visibility count", %{conn: conn} do +      admin = insert(:user, is_admin: true) +      user = insert(:user) +      CommonAPI.post(user, %{visibility: "public", status: "hey"}) +      CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) +      CommonAPI.post(user, %{visibility: "unlisted", status: "hey"}) + +      response = +        conn +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/stats") +        |> json_response(200) + +      assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} = +               response["status_visibility"] +    end + +    test "by instance", %{conn: conn} do +      admin = insert(:user, is_admin: true) +      user1 = insert(:user) +      instance2 = "instance2.tld" +      user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"}) + +      CommonAPI.post(user1, %{visibility: "public", status: "hey"}) +      CommonAPI.post(user2, %{visibility: "unlisted", status: "hey"}) +      CommonAPI.post(user2, %{visibility: "private", status: "hey"}) + +      response = +        conn +        |> assign(:user, admin) +        |> get("/api/pleroma/admin/stats", instance: instance2) +        |> json_response(200) + +      assert %{"direct" => 0, "private" => 1, "public" => 0, "unlisted" => 1} = +               response["status_visibility"] +    end +  end +end + +# Needed for testing +defmodule Pleroma.Web.Endpoint.NotReal do +end + +defmodule Pleroma.Captcha.NotReal do +end diff --git a/test/web/admin_api/controllers/config_controller_test.exs b/test/web/admin_api/controllers/config_controller_test.exs new file mode 100644 index 000000000..4e897455f --- /dev/null +++ b/test/web/admin_api/controllers/config_controller_test.exs @@ -0,0 +1,1465 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ConfigControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  import ExUnit.CaptureLog +  import Pleroma.Factory + +  alias Pleroma.Config +  alias Pleroma.ConfigDB + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "GET /api/pleroma/admin/config" do +    setup do: clear_config(:configurable_from_database, true) + +    test "when configuration from database is off", %{conn: conn} do +      Config.put(:configurable_from_database, false) +      conn = get(conn, "/api/pleroma/admin/config") + +      assert json_response_and_validate_schema(conn, 400) == +               %{ +                 "error" => "To use this endpoint you need to enable configuration from database." +               } +    end + +    test "with settings only in db", %{conn: conn} do +      config1 = insert(:config) +      config2 = insert(:config) + +      conn = get(conn, "/api/pleroma/admin/config?only_db=true") + +      %{ +        "configs" => [ +          %{ +            "group" => ":pleroma", +            "key" => key1, +            "value" => _ +          }, +          %{ +            "group" => ":pleroma", +            "key" => key2, +            "value" => _ +          } +        ] +      } = json_response_and_validate_schema(conn, 200) + +      assert key1 == inspect(config1.key) +      assert key2 == inspect(config2.key) +    end + +    test "db is added to settings that are in db", %{conn: conn} do +      _config = insert(:config, key: ":instance", value: [name: "Some name"]) + +      %{"configs" => configs} = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response_and_validate_schema(200) + +      [instance_config] = +        Enum.filter(configs, fn %{"group" => group, "key" => key} -> +          group == ":pleroma" and key == ":instance" +        end) + +      assert instance_config["db"] == [":name"] +    end + +    test "merged default setting with db settings", %{conn: conn} do +      config1 = insert(:config) +      config2 = insert(:config) + +      config3 = +        insert(:config, +          value: [k1: :v1, k2: :v2] +        ) + +      %{"configs" => configs} = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response_and_validate_schema(200) + +      assert length(configs) > 3 + +      saved_configs = [config1, config2, config3] +      keys = Enum.map(saved_configs, &inspect(&1.key)) + +      received_configs = +        Enum.filter(configs, fn %{"group" => group, "key" => key} -> +          group == ":pleroma" and key in keys +        end) + +      assert length(received_configs) == 3 + +      db_keys = +        config3.value +        |> Keyword.keys() +        |> ConfigDB.to_json_types() + +      keys = Enum.map(saved_configs -- [config3], &inspect(&1.key)) + +      values = Enum.map(saved_configs, &ConfigDB.to_json_types(&1.value)) + +      mapset_keys = MapSet.new(keys ++ db_keys) + +      Enum.each(received_configs, fn %{"value" => value, "db" => db} -> +        db = MapSet.new(db) +        assert MapSet.subset?(db, mapset_keys) + +        assert value in values +      end) +    end + +    test "subkeys with full update right merge", %{conn: conn} do +      insert(:config, +        key: ":emoji", +        value: [groups: [a: 1, b: 2], key: [a: 1]] +      ) + +      insert(:config, +        key: ":assets", +        value: [mascots: [a: 1, b: 2], key: [a: 1]] +      ) + +      %{"configs" => configs} = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response_and_validate_schema(200) + +      vals = +        Enum.filter(configs, fn %{"group" => group, "key" => key} -> +          group == ":pleroma" and key in [":emoji", ":assets"] +        end) + +      emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end) +      assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end) + +      emoji_val = ConfigDB.to_elixir_types(emoji["value"]) +      assets_val = ConfigDB.to_elixir_types(assets["value"]) + +      assert emoji_val[:groups] == [a: 1, b: 2] +      assert assets_val[:mascots] == [a: 1, b: 2] +    end + +    test "with valid `admin_token` query parameter, skips OAuth scopes check" do +      clear_config([:admin_token], "password123") + +      build_conn() +      |> get("/api/pleroma/admin/config?admin_token=password123") +      |> json_response_and_validate_schema(200) +    end +  end + +  test "POST /api/pleroma/admin/config error", %{conn: conn} do +    conn = +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/pleroma/admin/config", %{"configs" => []}) + +    assert json_response_and_validate_schema(conn, 400) == +             %{"error" => "To use this endpoint you need to enable configuration from database."} +  end + +  describe "POST /api/pleroma/admin/config" do +    setup do +      http = Application.get_env(:pleroma, :http) + +      on_exit(fn -> +        Application.delete_env(:pleroma, :key1) +        Application.delete_env(:pleroma, :key2) +        Application.delete_env(:pleroma, :key3) +        Application.delete_env(:pleroma, :key4) +        Application.delete_env(:pleroma, :keyaa1) +        Application.delete_env(:pleroma, :keyaa2) +        Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal) +        Application.delete_env(:pleroma, Pleroma.Captcha.NotReal) +        Application.put_env(:pleroma, :http, http) +        Application.put_env(:tesla, :adapter, Tesla.Mock) +        Restarter.Pleroma.refresh() +      end) +    end + +    setup do: clear_config(:configurable_from_database, true) + +    @tag capture_log: true +    test "create new config setting in db", %{conn: conn} do +      ueberauth = Application.get_env(:ueberauth, Ueberauth) +      on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{group: ":pleroma", key: ":key1", value: "value1"}, +            %{ +              group: ":ueberauth", +              key: "Ueberauth", +              value: [%{"tuple" => [":consumer_secret", "aaaa"]}] +            }, +            %{ +              group: ":pleroma", +              key: ":key2", +              value: %{ +                ":nested_1" => "nested_value1", +                ":nested_2" => [ +                  %{":nested_22" => "nested_value222"}, +                  %{":nested_33" => %{":nested_44" => "nested_444"}} +                ] +              } +            }, +            %{ +              group: ":pleroma", +              key: ":key3", +              value: [ +                %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, +                %{"nested_4" => true} +              ] +            }, +            %{ +              group: ":pleroma", +              key: ":key4", +              value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"} +            }, +            %{ +              group: ":idna", +              key: ":key5", +              value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]} +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => "value1", +                   "db" => [":key1"] +                 }, +                 %{ +                   "group" => ":ueberauth", +                   "key" => "Ueberauth", +                   "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}], +                   "db" => [":consumer_secret"] +                 }, +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key2", +                   "value" => %{ +                     ":nested_1" => "nested_value1", +                     ":nested_2" => [ +                       %{":nested_22" => "nested_value222"}, +                       %{":nested_33" => %{":nested_44" => "nested_444"}} +                     ] +                   }, +                   "db" => [":key2"] +                 }, +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key3", +                   "value" => [ +                     %{"nested_3" => ":nested_3", "nested_33" => "nested_33"}, +                     %{"nested_4" => true} +                   ], +                   "db" => [":key3"] +                 }, +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key4", +                   "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"}, +                   "db" => [":key4"] +                 }, +                 %{ +                   "group" => ":idna", +                   "key" => ":key5", +                   "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}, +                   "db" => [":key5"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Application.get_env(:pleroma, :key1) == "value1" + +      assert Application.get_env(:pleroma, :key2) == %{ +               nested_1: "nested_value1", +               nested_2: [ +                 %{nested_22: "nested_value222"}, +                 %{nested_33: %{nested_44: "nested_444"}} +               ] +             } + +      assert Application.get_env(:pleroma, :key3) == [ +               %{"nested_3" => :nested_3, "nested_33" => "nested_33"}, +               %{"nested_4" => true} +             ] + +      assert Application.get_env(:pleroma, :key4) == %{ +               "endpoint" => "https://example.com", +               nested_5: :upload +             } + +      assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []} +    end + +    test "save configs setting without explicit key", %{conn: conn} do +      level = Application.get_env(:quack, :level) +      meta = Application.get_env(:quack, :meta) +      webhook_url = Application.get_env(:quack, :webhook_url) + +      on_exit(fn -> +        Application.put_env(:quack, :level, level) +        Application.put_env(:quack, :meta, meta) +        Application.put_env(:quack, :webhook_url, webhook_url) +      end) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":quack", +              key: ":level", +              value: ":info" +            }, +            %{ +              group: ":quack", +              key: ":meta", +              value: [":none"] +            }, +            %{ +              group: ":quack", +              key: ":webhook_url", +              value: "https://hooks.slack.com/services/KEY" +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":quack", +                   "key" => ":level", +                   "value" => ":info", +                   "db" => [":level"] +                 }, +                 %{ +                   "group" => ":quack", +                   "key" => ":meta", +                   "value" => [":none"], +                   "db" => [":meta"] +                 }, +                 %{ +                   "group" => ":quack", +                   "key" => ":webhook_url", +                   "value" => "https://hooks.slack.com/services/KEY", +                   "db" => [":webhook_url"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Application.get_env(:quack, :level) == :info +      assert Application.get_env(:quack, :meta) == [:none] +      assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY" +    end + +    test "saving config with partial update", %{conn: conn} do +      insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2)) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]} +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{"tuple" => [":key1", 1]}, +                     %{"tuple" => [":key2", 2]}, +                     %{"tuple" => [":key3", 3]} +                   ], +                   "db" => [":key1", ":key2", ":key3"] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "saving config which need pleroma reboot", %{conn: conn} do +      chat = Config.get(:chat) +      on_exit(fn -> Config.put(:chat, chat) end) + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post( +               "/api/pleroma/admin/config", +               %{ +                 configs: [ +                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]} +                 ] +               } +             ) +             |> json_response_and_validate_schema(200) == %{ +               "configs" => [ +                 %{ +                   "db" => [":enabled"], +                   "group" => ":pleroma", +                   "key" => ":chat", +                   "value" => [%{"tuple" => [":enabled", true]}] +                 } +               ], +               "need_reboot" => true +             } + +      configs = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response_and_validate_schema(200) + +      assert configs["need_reboot"] + +      capture_log(fn -> +        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == +                 %{} +      end) =~ "pleroma restarted" + +      configs = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response_and_validate_schema(200) + +      assert configs["need_reboot"] == false +    end + +    test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do +      chat = Config.get(:chat) +      on_exit(fn -> Config.put(:chat, chat) end) + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post( +               "/api/pleroma/admin/config", +               %{ +                 configs: [ +                   %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]} +                 ] +               } +             ) +             |> json_response_and_validate_schema(200) == %{ +               "configs" => [ +                 %{ +                   "db" => [":enabled"], +                   "group" => ":pleroma", +                   "key" => ":chat", +                   "value" => [%{"tuple" => [":enabled", true]}] +                 } +               ], +               "need_reboot" => true +             } + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/pleroma/admin/config", %{ +               configs: [ +                 %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]} +               ] +             }) +             |> json_response_and_validate_schema(200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{"tuple" => [":key3", 3]} +                   ], +                   "db" => [":key3"] +                 } +               ], +               "need_reboot" => true +             } + +      capture_log(fn -> +        assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == +                 %{} +      end) =~ "pleroma restarted" + +      configs = +        conn +        |> get("/api/pleroma/admin/config") +        |> json_response_and_validate_schema(200) + +      assert configs["need_reboot"] == false +    end + +    test "saving config with nested merge", %{conn: conn} do +      insert(:config, key: :key1, value: [key1: 1, key2: [k1: 1, k2: 2]]) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":key1", +              value: [ +                %{"tuple" => [":key3", 3]}, +                %{ +                  "tuple" => [ +                    ":key2", +                    [ +                      %{"tuple" => [":k2", 1]}, +                      %{"tuple" => [":k3", 3]} +                    ] +                  ] +                } +              ] +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{"tuple" => [":key1", 1]}, +                     %{"tuple" => [":key3", 3]}, +                     %{ +                       "tuple" => [ +                         ":key2", +                         [ +                           %{"tuple" => [":k1", 1]}, +                           %{"tuple" => [":k2", 1]}, +                           %{"tuple" => [":k3", 3]} +                         ] +                       ] +                     } +                   ], +                   "db" => [":key1", ":key3", ":key2"] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "saving special atoms", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          "configs" => [ +            %{ +              "group" => ":pleroma", +              "key" => ":key1", +              "value" => [ +                %{ +                  "tuple" => [ +                    ":ssl_options", +                    [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}] +                  ] +                } +              ] +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":key1", +                   "value" => [ +                     %{ +                       "tuple" => [ +                         ":ssl_options", +                         [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}] +                       ] +                     } +                   ], +                   "db" => [":ssl_options"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Application.get_env(:pleroma, :key1) == [ +               ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]] +             ] +    end + +    test "saving full setting if value is in full_key_update list", %{conn: conn} do +      backends = Application.get_env(:logger, :backends) +      on_exit(fn -> Application.put_env(:logger, :backends, backends) end) + +      insert(:config, +        group: :logger, +        key: :backends, +        value: [] +      ) + +      Pleroma.Config.TransferTask.load_and_update_env([], false) + +      assert Application.get_env(:logger, :backends) == [] + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":logger", +              key: ":backends", +              value: [":console"] +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":logger", +                   "key" => ":backends", +                   "value" => [ +                     ":console" +                   ], +                   "db" => [":backends"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Application.get_env(:logger, :backends) == [ +               :console +             ] +    end + +    test "saving full setting if value is not keyword", %{conn: conn} do +      insert(:config, +        group: :tesla, +        key: :adapter, +        value: Tesla.Adapter.Hackey +      ) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{group: ":tesla", key: ":adapter", value: "Tesla.Adapter.Httpc"} +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":tesla", +                   "key" => ":adapter", +                   "value" => "Tesla.Adapter.Httpc", +                   "db" => [":adapter"] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "update config setting & delete with fallback to default value", %{ +      conn: conn, +      admin: admin, +      token: token +    } do +      ueberauth = Application.get_env(:ueberauth, Ueberauth) +      insert(:config, key: :keyaa1) +      insert(:config, key: :keyaa2) + +      config3 = +        insert(:config, +          group: :ueberauth, +          key: Ueberauth +        ) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{group: ":pleroma", key: ":keyaa1", value: "another_value"}, +            %{group: ":pleroma", key: ":keyaa2", value: "another_value"} +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":keyaa1", +                   "value" => "another_value", +                   "db" => [":keyaa1"] +                 }, +                 %{ +                   "group" => ":pleroma", +                   "key" => ":keyaa2", +                   "value" => "another_value", +                   "db" => [":keyaa2"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Application.get_env(:pleroma, :keyaa1) == "another_value" +      assert Application.get_env(:pleroma, :keyaa2) == "another_value" +      assert Application.get_env(:ueberauth, Ueberauth) == config3.value + +      conn = +        build_conn() +        |> assign(:user, admin) +        |> assign(:token, token) +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{group: ":pleroma", key: ":keyaa2", delete: true}, +            %{ +              group: ":ueberauth", +              key: "Ueberauth", +              delete: true +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [], +               "need_reboot" => false +             } + +      assert Application.get_env(:ueberauth, Ueberauth) == ueberauth +      refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2) +    end + +    test "common config example", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => ":pleroma", +              "key" => "Pleroma.Captcha.NotReal", +              "value" => [ +                %{"tuple" => [":enabled", false]}, +                %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, +                %{"tuple" => [":seconds_valid", 60]}, +                %{"tuple" => [":path", ""]}, +                %{"tuple" => [":key1", nil]}, +                %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}, +                %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]}, +                %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]}, +                %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]}, +                %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]}, +                %{"tuple" => [":name", "Pleroma"]} +              ] +            } +          ] +        }) + +      assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma" + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => "Pleroma.Captcha.NotReal", +                   "value" => [ +                     %{"tuple" => [":enabled", false]}, +                     %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]}, +                     %{"tuple" => [":seconds_valid", 60]}, +                     %{"tuple" => [":path", ""]}, +                     %{"tuple" => [":key1", nil]}, +                     %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]}, +                     %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]}, +                     %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]}, +                     %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]}, +                     %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]}, +                     %{"tuple" => [":name", "Pleroma"]} +                   ], +                   "db" => [ +                     ":enabled", +                     ":method", +                     ":seconds_valid", +                     ":path", +                     ":key1", +                     ":partial_chain", +                     ":regex1", +                     ":regex2", +                     ":regex3", +                     ":regex4", +                     ":name" +                   ] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "tuples with more than two values", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => ":pleroma", +              "key" => "Pleroma.Web.Endpoint.NotReal", +              "value" => [ +                %{ +                  "tuple" => [ +                    ":http", +                    [ +                      %{ +                        "tuple" => [ +                          ":key2", +                          [ +                            %{ +                              "tuple" => [ +                                ":_", +                                [ +                                  %{ +                                    "tuple" => [ +                                      "/api/v1/streaming", +                                      "Pleroma.Web.MastodonAPI.WebsocketHandler", +                                      [] +                                    ] +                                  }, +                                  %{ +                                    "tuple" => [ +                                      "/websocket", +                                      "Phoenix.Endpoint.CowboyWebSocket", +                                      %{ +                                        "tuple" => [ +                                          "Phoenix.Transports.WebSocket", +                                          %{ +                                            "tuple" => [ +                                              "Pleroma.Web.Endpoint", +                                              "Pleroma.Web.UserSocket", +                                              [] +                                            ] +                                          } +                                        ] +                                      } +                                    ] +                                  }, +                                  %{ +                                    "tuple" => [ +                                      ":_", +                                      "Phoenix.Endpoint.Cowboy2Handler", +                                      %{"tuple" => ["Pleroma.Web.Endpoint", []]} +                                    ] +                                  } +                                ] +                              ] +                            } +                          ] +                        ] +                      } +                    ] +                  ] +                } +              ] +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => "Pleroma.Web.Endpoint.NotReal", +                   "value" => [ +                     %{ +                       "tuple" => [ +                         ":http", +                         [ +                           %{ +                             "tuple" => [ +                               ":key2", +                               [ +                                 %{ +                                   "tuple" => [ +                                     ":_", +                                     [ +                                       %{ +                                         "tuple" => [ +                                           "/api/v1/streaming", +                                           "Pleroma.Web.MastodonAPI.WebsocketHandler", +                                           [] +                                         ] +                                       }, +                                       %{ +                                         "tuple" => [ +                                           "/websocket", +                                           "Phoenix.Endpoint.CowboyWebSocket", +                                           %{ +                                             "tuple" => [ +                                               "Phoenix.Transports.WebSocket", +                                               %{ +                                                 "tuple" => [ +                                                   "Pleroma.Web.Endpoint", +                                                   "Pleroma.Web.UserSocket", +                                                   [] +                                                 ] +                                               } +                                             ] +                                           } +                                         ] +                                       }, +                                       %{ +                                         "tuple" => [ +                                           ":_", +                                           "Phoenix.Endpoint.Cowboy2Handler", +                                           %{"tuple" => ["Pleroma.Web.Endpoint", []]} +                                         ] +                                       } +                                     ] +                                   ] +                                 } +                               ] +                             ] +                           } +                         ] +                       ] +                     } +                   ], +                   "db" => [":http"] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "settings with nesting map", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => ":pleroma", +              "key" => ":key1", +              "value" => [ +                %{"tuple" => [":key2", "some_val"]}, +                %{ +                  "tuple" => [ +                    ":key3", +                    %{ +                      ":max_options" => 20, +                      ":max_option_chars" => 200, +                      ":min_expiration" => 0, +                      ":max_expiration" => 31_536_000, +                      "nested" => %{ +                        ":max_options" => 20, +                        ":max_option_chars" => 200, +                        ":min_expiration" => 0, +                        ":max_expiration" => 31_536_000 +                      } +                    } +                  ] +                } +              ] +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == +               %{ +                 "configs" => [ +                   %{ +                     "group" => ":pleroma", +                     "key" => ":key1", +                     "value" => [ +                       %{"tuple" => [":key2", "some_val"]}, +                       %{ +                         "tuple" => [ +                           ":key3", +                           %{ +                             ":max_expiration" => 31_536_000, +                             ":max_option_chars" => 200, +                             ":max_options" => 20, +                             ":min_expiration" => 0, +                             "nested" => %{ +                               ":max_expiration" => 31_536_000, +                               ":max_option_chars" => 200, +                               ":max_options" => 20, +                               ":min_expiration" => 0 +                             } +                           } +                         ] +                       } +                     ], +                     "db" => [":key2", ":key3"] +                   } +                 ], +                 "need_reboot" => false +               } +    end + +    test "value as map", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => ":pleroma", +              "key" => ":key1", +              "value" => %{"key" => "some_val"} +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == +               %{ +                 "configs" => [ +                   %{ +                     "group" => ":pleroma", +                     "key" => ":key1", +                     "value" => %{"key" => "some_val"}, +                     "db" => [":key1"] +                   } +                 ], +                 "need_reboot" => false +               } +    end + +    test "queues key as atom", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              "group" => ":oban", +              "key" => ":queues", +              "value" => [ +                %{"tuple" => [":federator_incoming", 50]}, +                %{"tuple" => [":federator_outgoing", 50]}, +                %{"tuple" => [":web_push", 50]}, +                %{"tuple" => [":mailer", 10]}, +                %{"tuple" => [":transmogrifier", 20]}, +                %{"tuple" => [":scheduled_activities", 10]}, +                %{"tuple" => [":background", 5]} +              ] +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":oban", +                   "key" => ":queues", +                   "value" => [ +                     %{"tuple" => [":federator_incoming", 50]}, +                     %{"tuple" => [":federator_outgoing", 50]}, +                     %{"tuple" => [":web_push", 50]}, +                     %{"tuple" => [":mailer", 10]}, +                     %{"tuple" => [":transmogrifier", 20]}, +                     %{"tuple" => [":scheduled_activities", 10]}, +                     %{"tuple" => [":background", 5]} +                   ], +                   "db" => [ +                     ":federator_incoming", +                     ":federator_outgoing", +                     ":web_push", +                     ":mailer", +                     ":transmogrifier", +                     ":scheduled_activities", +                     ":background" +                   ] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "delete part of settings by atom subkeys", %{conn: conn} do +      insert(:config, +        key: :keyaa1, +        value: [subkey1: "val1", subkey2: "val2", subkey3: "val3"] +      ) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":keyaa1", +              subkeys: [":subkey1", ":subkey3"], +              delete: true +            } +          ] +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":keyaa1", +                   "value" => [%{"tuple" => [":subkey2", "val2"]}], +                   "db" => [":subkey2"] +                 } +               ], +               "need_reboot" => false +             } +    end + +    test "proxy tuple localhost", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":http", +              value: [ +                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} +              ] +            } +          ] +        }) + +      assert %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":http", +                   "value" => value, +                   "db" => db +                 } +               ] +             } = json_response_and_validate_schema(conn, 200) + +      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value +      assert ":proxy_url" in db +    end + +    test "proxy tuple domain", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":http", +              value: [ +                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} +              ] +            } +          ] +        }) + +      assert %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":http", +                   "value" => value, +                   "db" => db +                 } +               ] +             } = json_response_and_validate_schema(conn, 200) + +      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value +      assert ":proxy_url" in db +    end + +    test "proxy tuple ip", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/config", %{ +          configs: [ +            %{ +              group: ":pleroma", +              key: ":http", +              value: [ +                %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} +              ] +            } +          ] +        }) + +      assert %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => ":http", +                   "value" => value, +                   "db" => db +                 } +               ] +             } = json_response_and_validate_schema(conn, 200) + +      assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value +      assert ":proxy_url" in db +    end + +    @tag capture_log: true +    test "doesn't set keys not in the whitelist", %{conn: conn} do +      clear_config(:database_config_whitelist, [ +        {:pleroma, :key1}, +        {:pleroma, :key2}, +        {:pleroma, Pleroma.Captcha.NotReal}, +        {:not_real} +      ]) + +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/pleroma/admin/config", %{ +        configs: [ +          %{group: ":pleroma", key: ":key1", value: "value1"}, +          %{group: ":pleroma", key: ":key2", value: "value2"}, +          %{group: ":pleroma", key: ":key3", value: "value3"}, +          %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"}, +          %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"}, +          %{group: ":not_real", key: ":anything", value: "value6"} +        ] +      }) + +      assert Application.get_env(:pleroma, :key1) == "value1" +      assert Application.get_env(:pleroma, :key2) == "value2" +      assert Application.get_env(:pleroma, :key3) == nil +      assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil +      assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5" +      assert Application.get_env(:not_real, :anything) == "value6" +    end + +    test "args for Pleroma.Upload.Filter.Mogrify with custom tuples", %{conn: conn} do +      clear_config(Pleroma.Upload.Filter.Mogrify) + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/pleroma/admin/config", %{ +               configs: [ +                 %{ +                   group: ":pleroma", +                   key: "Pleroma.Upload.Filter.Mogrify", +                   value: [ +                     %{"tuple" => [":args", ["auto-orient", "strip"]]} +                   ] +                 } +               ] +             }) +             |> json_response_and_validate_schema(200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => "Pleroma.Upload.Filter.Mogrify", +                   "value" => [ +                     %{"tuple" => [":args", ["auto-orient", "strip"]]} +                   ], +                   "db" => [":args"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Config.get(Pleroma.Upload.Filter.Mogrify) == [args: ["auto-orient", "strip"]] + +      assert conn +             |> put_req_header("content-type", "application/json") +             |> post("/api/pleroma/admin/config", %{ +               configs: [ +                 %{ +                   group: ":pleroma", +                   key: "Pleroma.Upload.Filter.Mogrify", +                   value: [ +                     %{ +                       "tuple" => [ +                         ":args", +                         [ +                           "auto-orient", +                           "strip", +                           "{\"implode\", \"1\"}", +                           "{\"resize\", \"3840x1080>\"}" +                         ] +                       ] +                     } +                   ] +                 } +               ] +             }) +             |> json_response(200) == %{ +               "configs" => [ +                 %{ +                   "group" => ":pleroma", +                   "key" => "Pleroma.Upload.Filter.Mogrify", +                   "value" => [ +                     %{ +                       "tuple" => [ +                         ":args", +                         [ +                           "auto-orient", +                           "strip", +                           "{\"implode\", \"1\"}", +                           "{\"resize\", \"3840x1080>\"}" +                         ] +                       ] +                     } +                   ], +                   "db" => [":args"] +                 } +               ], +               "need_reboot" => false +             } + +      assert Config.get(Pleroma.Upload.Filter.Mogrify) == [ +               args: ["auto-orient", "strip", {"implode", "1"}, {"resize", "3840x1080>"}] +             ] +    end + +    test "enables the welcome messages", %{conn: conn} do +      clear_config([:welcome]) + +      params = %{ +        "group" => ":pleroma", +        "key" => ":welcome", +        "value" => [ +          %{ +            "tuple" => [ +              ":direct_message", +              [ +                %{"tuple" => [":enabled", true]}, +                %{"tuple" => [":message", "Welcome to Pleroma!"]}, +                %{"tuple" => [":sender_nickname", "pleroma"]} +              ] +            ] +          }, +          %{ +            "tuple" => [ +              ":chat_message", +              [ +                %{"tuple" => [":enabled", true]}, +                %{"tuple" => [":message", "Welcome to Pleroma!"]}, +                %{"tuple" => [":sender_nickname", "pleroma"]} +              ] +            ] +          }, +          %{ +            "tuple" => [ +              ":email", +              [ +                %{"tuple" => [":enabled", true]}, +                %{"tuple" => [":sender", %{"tuple" => ["pleroma@dev.dev", "Pleroma"]}]}, +                %{"tuple" => [":subject", "Welcome to <%= instance_name %>!"]}, +                %{"tuple" => [":html", "Welcome to <%= instance_name %>!"]}, +                %{"tuple" => [":text", "Welcome to <%= instance_name %>!"]} +              ] +            ] +          } +        ] +      } + +      refute Pleroma.User.WelcomeEmail.enabled?() +      refute Pleroma.User.WelcomeMessage.enabled?() +      refute Pleroma.User.WelcomeChatMessage.enabled?() + +      res = +        assert conn +               |> put_req_header("content-type", "application/json") +               |> post("/api/pleroma/admin/config", %{"configs" => [params]}) +               |> json_response_and_validate_schema(200) + +      assert Pleroma.User.WelcomeEmail.enabled?() +      assert Pleroma.User.WelcomeMessage.enabled?() +      assert Pleroma.User.WelcomeChatMessage.enabled?() + +      assert res == %{ +               "configs" => [ +                 %{ +                   "db" => [":direct_message", ":chat_message", ":email"], +                   "group" => ":pleroma", +                   "key" => ":welcome", +                   "value" => params["value"] +                 } +               ], +               "need_reboot" => false +             } +    end +  end + +  describe "GET /api/pleroma/admin/config/descriptions" do +    test "structure", %{conn: conn} do +      admin = insert(:user, is_admin: true) + +      conn = +        assign(conn, :user, admin) +        |> get("/api/pleroma/admin/config/descriptions") + +      assert [child | _others] = json_response_and_validate_schema(conn, 200) + +      assert child["children"] +      assert child["key"] +      assert String.starts_with?(child["group"], ":") +      assert child["description"] +    end + +    test "filters by database configuration whitelist", %{conn: conn} do +      clear_config(:database_config_whitelist, [ +        {:pleroma, :instance}, +        {:pleroma, :activitypub}, +        {:pleroma, Pleroma.Upload}, +        {:esshd} +      ]) + +      admin = insert(:user, is_admin: true) + +      conn = +        assign(conn, :user, admin) +        |> get("/api/pleroma/admin/config/descriptions") + +      children = json_response_and_validate_schema(conn, 200) + +      assert length(children) == 4 + +      assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3 + +      instance = Enum.find(children, fn c -> c["key"] == ":instance" end) +      assert instance["children"] + +      activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end) +      assert activitypub["children"] + +      web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end) +      assert web_endpoint["children"] + +      esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end) +      assert esshd["children"] +    end +  end +end diff --git a/test/web/admin_api/controllers/invite_controller_test.exs b/test/web/admin_api/controllers/invite_controller_test.exs new file mode 100644 index 000000000..ab186c5e7 --- /dev/null +++ b/test/web/admin_api/controllers/invite_controller_test.exs @@ -0,0 +1,281 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.InviteControllerTest do +  use Pleroma.Web.ConnCase, async: true + +  import Pleroma.Factory + +  alias Pleroma.Config +  alias Pleroma.Repo +  alias Pleroma.UserInviteToken + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "POST /api/pleroma/admin/users/email_invite, with valid config" do +    setup do: clear_config([:instance, :registrations_open], false) +    setup do: clear_config([:instance, :invites_enabled], true) + +    test "sends invitation and returns 204", %{admin: admin, conn: conn} do +      recipient_email = "foo@bar.com" +      recipient_name = "J. D." + +      conn = +        conn +        |> put_req_header("content-type", "application/json;charset=utf-8") +        |> post("/api/pleroma/admin/users/email_invite", %{ +          email: recipient_email, +          name: recipient_name +        }) + +      assert json_response_and_validate_schema(conn, :no_content) + +      token_record = List.last(Repo.all(Pleroma.UserInviteToken)) +      assert token_record +      refute token_record.used + +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      email = +        Pleroma.Emails.UserEmail.user_invitation_email( +          admin, +          token_record, +          recipient_email, +          recipient_name +        ) + +      Swoosh.TestAssertions.assert_email_sent( +        from: {instance_name, notify_email}, +        to: {recipient_name, recipient_email}, +        html_body: email.html_body +      ) +    end + +    test "it returns 403 if requested by a non-admin" do +      non_admin_user = insert(:user) +      token = insert(:oauth_token, user: non_admin_user) + +      conn = +        build_conn() +        |> assign(:user, non_admin_user) +        |> assign(:token, token) +        |> put_req_header("content-type", "application/json;charset=utf-8") +        |> post("/api/pleroma/admin/users/email_invite", %{ +          email: "foo@bar.com", +          name: "JD" +        }) + +      assert json_response(conn, :forbidden) +    end + +    test "email with +", %{conn: conn, admin: admin} do +      recipient_email = "foo+bar@baz.com" + +      conn +      |> put_req_header("content-type", "application/json;charset=utf-8") +      |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email}) +      |> json_response_and_validate_schema(:no_content) + +      token_record = +        Pleroma.UserInviteToken +        |> Repo.all() +        |> List.last() + +      assert token_record +      refute token_record.used + +      notify_email = Config.get([:instance, :notify_email]) +      instance_name = Config.get([:instance, :name]) + +      email = +        Pleroma.Emails.UserEmail.user_invitation_email( +          admin, +          token_record, +          recipient_email +        ) + +      Swoosh.TestAssertions.assert_email_sent( +        from: {instance_name, notify_email}, +        to: recipient_email, +        html_body: email.html_body +      ) +    end +  end + +  describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do +    setup do: clear_config([:instance, :registrations_open]) +    setup do: clear_config([:instance, :invites_enabled]) + +    test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do +      Config.put([:instance, :registrations_open], false) +      Config.put([:instance, :invites_enabled], false) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/email_invite", %{ +          email: "foo@bar.com", +          name: "JD" +        }) + +      assert json_response_and_validate_schema(conn, :bad_request) == +               %{ +                 "error" => +                   "To send invites you need to set the `invites_enabled` option to true." +               } +    end + +    test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do +      Config.put([:instance, :registrations_open], true) +      Config.put([:instance, :invites_enabled], true) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/email_invite", %{ +          email: "foo@bar.com", +          name: "JD" +        }) + +      assert json_response_and_validate_schema(conn, :bad_request) == +               %{ +                 "error" => +                   "To send invites you need to set the `registrations_open` option to false." +               } +    end +  end + +  describe "POST /api/pleroma/admin/users/invite_token" do +    test "without options", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/invite_token") + +      invite_json = json_response_and_validate_schema(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"]) +      refute invite.used +      refute invite.expires_at +      refute invite.max_use +      assert invite.invite_type == "one_time" +    end + +    test "with expires_at", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/invite_token", %{ +          "expires_at" => Date.to_string(Date.utc_today()) +        }) + +      invite_json = json_response_and_validate_schema(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"]) + +      refute invite.used +      assert invite.expires_at == Date.utc_today() +      refute invite.max_use +      assert invite.invite_type == "date_limited" +    end + +    test "with max_use", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/invite_token", %{"max_use" => 150}) + +      invite_json = json_response_and_validate_schema(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"]) +      refute invite.used +      refute invite.expires_at +      assert invite.max_use == 150 +      assert invite.invite_type == "reusable" +    end + +    test "with max use and expires_at", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/invite_token", %{ +          "max_use" => 150, +          "expires_at" => Date.to_string(Date.utc_today()) +        }) + +      invite_json = json_response_and_validate_schema(conn, 200) +      invite = UserInviteToken.find_by_token!(invite_json["token"]) +      refute invite.used +      assert invite.expires_at == Date.utc_today() +      assert invite.max_use == 150 +      assert invite.invite_type == "reusable_date_limited" +    end +  end + +  describe "GET /api/pleroma/admin/users/invites" do +    test "no invites", %{conn: conn} do +      conn = get(conn, "/api/pleroma/admin/users/invites") + +      assert json_response_and_validate_schema(conn, 200) == %{"invites" => []} +    end + +    test "with invite", %{conn: conn} do +      {:ok, invite} = UserInviteToken.create_invite() + +      conn = get(conn, "/api/pleroma/admin/users/invites") + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "invites" => [ +                 %{ +                   "expires_at" => nil, +                   "id" => invite.id, +                   "invite_type" => "one_time", +                   "max_use" => nil, +                   "token" => invite.token, +                   "used" => false, +                   "uses" => 0 +                 } +               ] +             } +    end +  end + +  describe "POST /api/pleroma/admin/users/revoke_invite" do +    test "with token", %{conn: conn} do +      {:ok, invite} = UserInviteToken.create_invite() + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token}) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "expires_at" => nil, +               "id" => invite.id, +               "invite_type" => "one_time", +               "max_use" => nil, +               "token" => invite.token, +               "used" => true, +               "uses" => 0 +             } +    end + +    test "with invalid token", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"}) + +      assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"} +    end +  end +end diff --git a/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs b/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs new file mode 100644 index 000000000..f243d1fb2 --- /dev/null +++ b/test/web/admin_api/controllers/media_proxy_cache_controller_test.exs @@ -0,0 +1,167 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.MediaProxyCacheControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory +  import Mock + +  alias Pleroma.Web.MediaProxy + +  setup do: clear_config([:media_proxy]) + +  setup do +    on_exit(fn -> Cachex.clear(:banned_urls_cache) end) +  end + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    Config.put([:media_proxy, :enabled], true) +    Config.put([:media_proxy, :invalidation, :enabled], true) +    Config.put([:media_proxy, :invalidation, :provider], MediaProxy.Invalidation.Script) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "GET /api/pleroma/admin/media_proxy_caches" do +    test "shows banned MediaProxy URLs", %{conn: conn} do +      MediaProxy.put_in_banned_urls([ +        "http://localhost:4001/media/a688346.jpg", +        "http://localhost:4001/media/fb1f4d.jpg" +      ]) + +      MediaProxy.put_in_banned_urls("http://localhost:4001/media/gb1f44.jpg") +      MediaProxy.put_in_banned_urls("http://localhost:4001/media/tb13f47.jpg") +      MediaProxy.put_in_banned_urls("http://localhost:4001/media/wb1f46.jpg") + +      response = +        conn +        |> get("/api/pleroma/admin/media_proxy_caches?page_size=2") +        |> json_response_and_validate_schema(200) + +      assert response["page_size"] == 2 +      assert response["count"] == 5 + +      assert response["urls"] == [ +               "http://localhost:4001/media/fb1f4d.jpg", +               "http://localhost:4001/media/a688346.jpg" +             ] + +      response = +        conn +        |> get("/api/pleroma/admin/media_proxy_caches?page_size=2&page=2") +        |> json_response_and_validate_schema(200) + +      assert response["urls"] == [ +               "http://localhost:4001/media/gb1f44.jpg", +               "http://localhost:4001/media/tb13f47.jpg" +             ] + +      assert response["page_size"] == 2 +      assert response["count"] == 5 + +      response = +        conn +        |> get("/api/pleroma/admin/media_proxy_caches?page_size=2&page=3") +        |> json_response_and_validate_schema(200) + +      assert response["urls"] == ["http://localhost:4001/media/wb1f46.jpg"] +    end + +    test "search banned MediaProxy URLs", %{conn: conn} do +      MediaProxy.put_in_banned_urls([ +        "http://localhost:4001/media/a688346.jpg", +        "http://localhost:4001/media/ff44b1f4d.jpg" +      ]) + +      MediaProxy.put_in_banned_urls("http://localhost:4001/media/gb1f44.jpg") +      MediaProxy.put_in_banned_urls("http://localhost:4001/media/tb13f47.jpg") +      MediaProxy.put_in_banned_urls("http://localhost:4001/media/wb1f46.jpg") + +      response = +        conn +        |> get("/api/pleroma/admin/media_proxy_caches?page_size=2&query=F44") +        |> json_response_and_validate_schema(200) + +      assert response["urls"] == [ +               "http://localhost:4001/media/gb1f44.jpg", +               "http://localhost:4001/media/ff44b1f4d.jpg" +             ] + +      assert response["page_size"] == 2 +      assert response["count"] == 2 +    end +  end + +  describe "POST /api/pleroma/admin/media_proxy_caches/delete" do +    test "deleted MediaProxy URLs from banned", %{conn: conn} do +      MediaProxy.put_in_banned_urls([ +        "http://localhost:4001/media/a688346.jpg", +        "http://localhost:4001/media/fb1f4d.jpg" +      ]) + +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/pleroma/admin/media_proxy_caches/delete", %{ +        urls: ["http://localhost:4001/media/a688346.jpg"] +      }) +      |> json_response_and_validate_schema(200) + +      refute MediaProxy.in_banned_urls("http://localhost:4001/media/a688346.jpg") +      assert MediaProxy.in_banned_urls("http://localhost:4001/media/fb1f4d.jpg") +    end +  end + +  describe "POST /api/pleroma/admin/media_proxy_caches/purge" do +    test "perform invalidates cache of MediaProxy", %{conn: conn} do +      urls = [ +        "http://example.com/media/a688346.jpg", +        "http://example.com/media/fb1f4d.jpg" +      ] + +      with_mocks [ +        {MediaProxy.Invalidation.Script, [], +         [ +           purge: fn _, _ -> {"ok", 0} end +         ]} +      ] do +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/media_proxy_caches/purge", %{urls: urls, ban: false}) +        |> json_response_and_validate_schema(200) + +        refute MediaProxy.in_banned_urls("http://example.com/media/a688346.jpg") +        refute MediaProxy.in_banned_urls("http://example.com/media/fb1f4d.jpg") +      end +    end + +    test "perform invalidates cache of MediaProxy and adds url to banned", %{conn: conn} do +      urls = [ +        "http://example.com/media/a688346.jpg", +        "http://example.com/media/fb1f4d.jpg" +      ] + +      with_mocks [{MediaProxy.Invalidation.Script, [], [purge: fn _, _ -> {"ok", 0} end]}] do +        conn +        |> put_req_header("content-type", "application/json") +        |> post( +          "/api/pleroma/admin/media_proxy_caches/purge", +          %{urls: urls, ban: true} +        ) +        |> json_response_and_validate_schema(200) + +        assert MediaProxy.in_banned_urls("http://example.com/media/a688346.jpg") +        assert MediaProxy.in_banned_urls("http://example.com/media/fb1f4d.jpg") +      end +    end +  end +end diff --git a/test/web/admin_api/controllers/oauth_app_controller_test.exs b/test/web/admin_api/controllers/oauth_app_controller_test.exs new file mode 100644 index 000000000..ed7c4172c --- /dev/null +++ b/test/web/admin_api/controllers/oauth_app_controller_test.exs @@ -0,0 +1,220 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.OAuthAppControllerTest do +  use Pleroma.Web.ConnCase, async: true +  use Oban.Testing, repo: Pleroma.Repo + +  import Pleroma.Factory + +  alias Pleroma.Config +  alias Pleroma.Web + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "POST /api/pleroma/admin/oauth_app" do +    test "errors", %{conn: conn} do +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/oauth_app", %{}) +        |> json_response_and_validate_schema(400) + +      assert %{ +               "error" => "Missing field: name. Missing field: redirect_uris." +             } = response +    end + +    test "success", %{conn: conn} do +      base_url = Web.base_url() +      app_name = "Trusted app" + +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/oauth_app", %{ +          name: app_name, +          redirect_uris: base_url +        }) +        |> json_response_and_validate_schema(200) + +      assert %{ +               "client_id" => _, +               "client_secret" => _, +               "name" => ^app_name, +               "redirect_uri" => ^base_url, +               "trusted" => false +             } = response +    end + +    test "with trusted", %{conn: conn} do +      base_url = Web.base_url() +      app_name = "Trusted app" + +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/oauth_app", %{ +          name: app_name, +          redirect_uris: base_url, +          trusted: true +        }) +        |> json_response_and_validate_schema(200) + +      assert %{ +               "client_id" => _, +               "client_secret" => _, +               "name" => ^app_name, +               "redirect_uri" => ^base_url, +               "trusted" => true +             } = response +    end +  end + +  describe "GET /api/pleroma/admin/oauth_app" do +    setup do +      app = insert(:oauth_app) +      {:ok, app: app} +    end + +    test "list", %{conn: conn} do +      response = +        conn +        |> get("/api/pleroma/admin/oauth_app") +        |> json_response_and_validate_schema(200) + +      assert %{"apps" => apps, "count" => count, "page_size" => _} = response + +      assert length(apps) == count +    end + +    test "with page size", %{conn: conn} do +      insert(:oauth_app) +      page_size = 1 + +      response = +        conn +        |> get("/api/pleroma/admin/oauth_app?page_size=#{page_size}") +        |> json_response_and_validate_schema(200) + +      assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response + +      assert length(apps) == page_size +    end + +    test "search by client name", %{conn: conn, app: app} do +      response = +        conn +        |> get("/api/pleroma/admin/oauth_app?name=#{app.client_name}") +        |> json_response_and_validate_schema(200) + +      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response + +      assert returned["client_id"] == app.client_id +      assert returned["name"] == app.client_name +    end + +    test "search by client id", %{conn: conn, app: app} do +      response = +        conn +        |> get("/api/pleroma/admin/oauth_app?client_id=#{app.client_id}") +        |> json_response_and_validate_schema(200) + +      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response + +      assert returned["client_id"] == app.client_id +      assert returned["name"] == app.client_name +    end + +    test "only trusted", %{conn: conn} do +      app = insert(:oauth_app, trusted: true) + +      response = +        conn +        |> get("/api/pleroma/admin/oauth_app?trusted=true") +        |> json_response_and_validate_schema(200) + +      assert %{"apps" => [returned], "count" => _, "page_size" => _} = response + +      assert returned["client_id"] == app.client_id +      assert returned["name"] == app.client_name +    end +  end + +  describe "DELETE /api/pleroma/admin/oauth_app/:id" do +    test "with id", %{conn: conn} do +      app = insert(:oauth_app) + +      response = +        conn +        |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id)) +        |> json_response_and_validate_schema(:no_content) + +      assert response == "" +    end + +    test "with non existance id", %{conn: conn} do +      response = +        conn +        |> delete("/api/pleroma/admin/oauth_app/0") +        |> json_response_and_validate_schema(:bad_request) + +      assert response == "" +    end +  end + +  describe "PATCH /api/pleroma/admin/oauth_app/:id" do +    test "with id", %{conn: conn} do +      app = insert(:oauth_app) + +      name = "another name" +      url = "https://example.com" +      scopes = ["admin"] +      id = app.id +      website = "http://website.com" + +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> patch("/api/pleroma/admin/oauth_app/#{id}", %{ +          name: name, +          trusted: true, +          redirect_uris: url, +          scopes: scopes, +          website: website +        }) +        |> json_response_and_validate_schema(200) + +      assert %{ +               "client_id" => _, +               "client_secret" => _, +               "id" => ^id, +               "name" => ^name, +               "redirect_uri" => ^url, +               "trusted" => true, +               "website" => ^website +             } = response +    end + +    test "without id", %{conn: conn} do +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> patch("/api/pleroma/admin/oauth_app/0") +        |> json_response_and_validate_schema(:bad_request) + +      assert response == "" +    end +  end +end diff --git a/test/web/admin_api/controllers/relay_controller_test.exs b/test/web/admin_api/controllers/relay_controller_test.exs new file mode 100644 index 000000000..adadf2b5c --- /dev/null +++ b/test/web/admin_api/controllers/relay_controller_test.exs @@ -0,0 +1,99 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.RelayControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory + +  alias Pleroma.Config +  alias Pleroma.ModerationLog +  alias Pleroma.Repo +  alias Pleroma.User + +  setup_all do +    Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + +    :ok +  end + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "relays" do +    test "POST /relay", %{conn: conn, admin: admin} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> post("/api/pleroma/admin/relay", %{ +          relay_url: "http://mastodon.example.org/users/admin" +        }) + +      assert json_response_and_validate_schema(conn, 200) == %{ +               "actor" => "http://mastodon.example.org/users/admin", +               "followed_back" => false +             } + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" +    end + +    test "GET /relay", %{conn: conn} do +      relay_user = Pleroma.Web.ActivityPub.Relay.get_actor() + +      ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"] +      |> Enum.each(fn ap_id -> +        {:ok, user} = User.get_or_fetch_by_ap_id(ap_id) +        User.follow(relay_user, user) +      end) + +      conn = get(conn, "/api/pleroma/admin/relay") + +      assert json_response_and_validate_schema(conn, 200)["relays"] == [ +               %{ +                 "actor" => "http://mastodon.example.org/users/admin", +                 "followed_back" => true +               }, +               %{"actor" => "https://mstdn.io/users/mayuutann", "followed_back" => true} +             ] +    end + +    test "DELETE /relay", %{conn: conn, admin: admin} do +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/pleroma/admin/relay", %{ +        relay_url: "http://mastodon.example.org/users/admin" +      }) + +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> delete("/api/pleroma/admin/relay", %{ +          relay_url: "http://mastodon.example.org/users/admin" +        }) + +      assert json_response_and_validate_schema(conn, 200) == +               "http://mastodon.example.org/users/admin" + +      [log_entry_one, log_entry_two] = Repo.all(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry_one) == +               "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin" + +      assert ModerationLog.get_log_entry_message(log_entry_two) == +               "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin" +    end +  end +end diff --git a/test/web/admin_api/controllers/report_controller_test.exs b/test/web/admin_api/controllers/report_controller_test.exs new file mode 100644 index 000000000..57946e6bb --- /dev/null +++ b/test/web/admin_api/controllers/report_controller_test.exs @@ -0,0 +1,372 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.ReportControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory + +  alias Pleroma.Activity +  alias Pleroma.Config +  alias Pleroma.ModerationLog +  alias Pleroma.Repo +  alias Pleroma.ReportNote +  alias Pleroma.Web.CommonAPI + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  end + +  describe "GET /api/pleroma/admin/reports/:id" do +    test "returns report by its id", %{conn: conn} do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %{id: report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I feel offended", +          status_ids: [activity.id] +        }) + +      response = +        conn +        |> get("/api/pleroma/admin/reports/#{report_id}") +        |> json_response_and_validate_schema(:ok) + +      assert response["id"] == report_id +    end + +    test "returns 404 when report id is invalid", %{conn: conn} do +      conn = get(conn, "/api/pleroma/admin/reports/test") + +      assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"} +    end +  end + +  describe "PATCH /api/pleroma/admin/reports" do +    setup do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %{id: report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I feel offended", +          status_ids: [activity.id] +        }) + +      {:ok, %{id: second_report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I feel very offended", +          status_ids: [activity.id] +        }) + +      %{ +        id: report_id, +        second_report_id: second_report_id +      } +    end + +    test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do +      read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"]) +      write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"]) + +      response = +        conn +        |> assign(:token, read_token) +        |> put_req_header("content-type", "application/json") +        |> patch("/api/pleroma/admin/reports", %{ +          "reports" => [%{"state" => "resolved", "id" => id}] +        }) +        |> json_response_and_validate_schema(403) + +      assert response == %{ +               "error" => "Insufficient permissions: admin:write:reports." +             } + +      conn +      |> assign(:token, write_token) +      |> put_req_header("content-type", "application/json") +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [%{"state" => "resolved", "id" => id}] +      }) +      |> json_response_and_validate_schema(:no_content) +    end + +    test "mark report as resolved", %{conn: conn, id: id, admin: admin} do +      conn +      |> put_req_header("content-type", "application/json") +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [ +          %{"state" => "resolved", "id" => id} +        ] +      }) +      |> json_response_and_validate_schema(:no_content) + +      activity = Activity.get_by_id(id) +      assert activity.data["state"] == "resolved" + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated report ##{id} with 'resolved' state" +    end + +    test "closes report", %{conn: conn, id: id, admin: admin} do +      conn +      |> put_req_header("content-type", "application/json") +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [ +          %{"state" => "closed", "id" => id} +        ] +      }) +      |> json_response_and_validate_schema(:no_content) + +      activity = Activity.get_by_id(id) +      assert activity.data["state"] == "closed" + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated report ##{id} with 'closed' state" +    end + +    test "returns 400 when state is unknown", %{conn: conn, id: id} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> patch("/api/pleroma/admin/reports", %{ +          "reports" => [ +            %{"state" => "test", "id" => id} +          ] +        }) + +      assert "Unsupported state" = +               hd(json_response_and_validate_schema(conn, :bad_request))["error"] +    end + +    test "returns 404 when report is not exist", %{conn: conn} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> patch("/api/pleroma/admin/reports", %{ +          "reports" => [ +            %{"state" => "closed", "id" => "test"} +          ] +        }) + +      assert hd(json_response_and_validate_schema(conn, :bad_request))["error"] == "not_found" +    end + +    test "updates state of multiple reports", %{ +      conn: conn, +      id: id, +      admin: admin, +      second_report_id: second_report_id +    } do +      conn +      |> put_req_header("content-type", "application/json") +      |> patch("/api/pleroma/admin/reports", %{ +        "reports" => [ +          %{"state" => "resolved", "id" => id}, +          %{"state" => "closed", "id" => second_report_id} +        ] +      }) +      |> json_response_and_validate_schema(:no_content) + +      activity = Activity.get_by_id(id) +      second_activity = Activity.get_by_id(second_report_id) +      assert activity.data["state"] == "resolved" +      assert second_activity.data["state"] == "closed" + +      [first_log_entry, second_log_entry] = Repo.all(ModerationLog) + +      assert ModerationLog.get_log_entry_message(first_log_entry) == +               "@#{admin.nickname} updated report ##{id} with 'resolved' state" + +      assert ModerationLog.get_log_entry_message(second_log_entry) == +               "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state" +    end +  end + +  describe "GET /api/pleroma/admin/reports" do +    test "returns empty response when no reports created", %{conn: conn} do +      response = +        conn +        |> get(report_path(conn, :index)) +        |> json_response_and_validate_schema(:ok) + +      assert Enum.empty?(response["reports"]) +      assert response["total"] == 0 +    end + +    test "returns reports", %{conn: conn} do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %{id: report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I feel offended", +          status_ids: [activity.id] +        }) + +      response = +        conn +        |> get(report_path(conn, :index)) +        |> json_response_and_validate_schema(:ok) + +      [report] = response["reports"] + +      assert length(response["reports"]) == 1 +      assert report["id"] == report_id + +      assert response["total"] == 1 +    end + +    test "returns reports with specified state", %{conn: conn} do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %{id: first_report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I feel offended", +          status_ids: [activity.id] +        }) + +      {:ok, %{id: second_report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I don't like this user" +        }) + +      CommonAPI.update_report_state(second_report_id, "closed") + +      response = +        conn +        |> get(report_path(conn, :index, %{state: "open"})) +        |> json_response_and_validate_schema(:ok) + +      assert [open_report] = response["reports"] + +      assert length(response["reports"]) == 1 +      assert open_report["id"] == first_report_id + +      assert response["total"] == 1 + +      response = +        conn +        |> get(report_path(conn, :index, %{state: "closed"})) +        |> json_response_and_validate_schema(:ok) + +      assert [closed_report] = response["reports"] + +      assert length(response["reports"]) == 1 +      assert closed_report["id"] == second_report_id + +      assert response["total"] == 1 + +      assert %{"total" => 0, "reports" => []} == +               conn +               |> get(report_path(conn, :index, %{state: "resolved"})) +               |> json_response_and_validate_schema(:ok) +    end + +    test "returns 403 when requested by a non-admin" do +      user = insert(:user) +      token = insert(:oauth_token, user: user) + +      conn = +        build_conn() +        |> assign(:user, user) +        |> assign(:token, token) +        |> get("/api/pleroma/admin/reports") + +      assert json_response(conn, :forbidden) == +               %{"error" => "User is not an admin."} +    end + +    test "returns 403 when requested by anonymous" do +      conn = get(build_conn(), "/api/pleroma/admin/reports") + +      assert json_response(conn, :forbidden) == %{ +               "error" => "Invalid credentials." +             } +    end +  end + +  describe "POST /api/pleroma/admin/reports/:id/notes" do +    setup %{conn: conn, admin: admin} do +      [reporter, target_user] = insert_pair(:user) +      activity = insert(:note_activity, user: target_user) + +      {:ok, %{id: report_id}} = +        CommonAPI.report(reporter, %{ +          account_id: target_user.id, +          comment: "I feel offended", +          status_ids: [activity.id] +        }) + +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ +        content: "this is disgusting!" +      }) + +      conn +      |> put_req_header("content-type", "application/json") +      |> post("/api/pleroma/admin/reports/#{report_id}/notes", %{ +        content: "this is disgusting2!" +      }) + +      %{ +        admin_id: admin.id, +        report_id: report_id +      } +    end + +    test "it creates report note", %{admin_id: admin_id, report_id: report_id} do +      assert [note, _] = Repo.all(ReportNote) + +      assert %{ +               activity_id: ^report_id, +               content: "this is disgusting!", +               user_id: ^admin_id +             } = note +    end + +    test "it returns reports with notes", %{conn: conn, admin: admin} do +      conn = get(conn, "/api/pleroma/admin/reports") + +      response = json_response_and_validate_schema(conn, 200) +      notes = hd(response["reports"])["notes"] +      [note, _] = notes + +      assert note["user"]["nickname"] == admin.nickname +      assert note["content"] == "this is disgusting!" +      assert note["created_at"] +      assert response["total"] == 1 +    end + +    test "it deletes the note", %{conn: conn, report_id: report_id} do +      assert ReportNote |> Repo.all() |> length() == 2 +      assert [note, _] = Repo.all(ReportNote) + +      delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}") + +      assert ReportNote |> Repo.all() |> length() == 1 +    end +  end +end diff --git a/test/web/admin_api/controllers/status_controller_test.exs b/test/web/admin_api/controllers/status_controller_test.exs new file mode 100644 index 000000000..eff78fb0a --- /dev/null +++ b/test/web/admin_api/controllers/status_controller_test.exs @@ -0,0 +1,202 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusControllerTest do +  use Pleroma.Web.ConnCase + +  import Pleroma.Factory + +  alias Pleroma.Activity +  alias Pleroma.Config +  alias Pleroma.ModerationLog +  alias Pleroma.Repo +  alias Pleroma.User +  alias Pleroma.Web.CommonAPI + +  setup do +    admin = insert(:user, is_admin: true) +    token = insert(:oauth_admin_token, user: admin) + +    conn = +      build_conn() +      |> assign(:user, admin) +      |> assign(:token, token) + +    {:ok, %{admin: admin, token: token, conn: conn}} +  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_and_validate_schema(:not_found) +    end + +    test "shows activity", %{conn: conn} do +      activity = insert(:note_activity) + +      response = +        conn +        |> get("/api/pleroma/admin/statuses/#{activity.id}") +        |> json_response_and_validate_schema(200) + +      assert response["id"] == activity.id + +      account = response["account"] +      actor = User.get_by_ap_id(activity.actor) + +      assert account["id"] == actor.id +      assert account["nickname"] == actor.nickname +      assert account["deactivated"] == actor.deactivated +      assert account["confirmation_pending"] == actor.confirmation_pending +    end +  end + +  describe "PUT /api/pleroma/admin/statuses/:id" do +    setup do +      activity = insert(:note_activity) + +      %{id: activity.id} +    end + +    test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) +        |> json_response_and_validate_schema(:ok) + +      assert response["sensitive"] + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" + +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) +        |> json_response_and_validate_schema(:ok) + +      refute response["sensitive"] +    end + +    test "change visibility flag", %{conn: conn, id: id, admin: admin} do +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"}) +        |> json_response_and_validate_schema(:ok) + +      assert response["visibility"] == "public" + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" + +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"}) +        |> json_response_and_validate_schema(:ok) + +      assert response["visibility"] == "private" + +      response = +        conn +        |> put_req_header("content-type", "application/json") +        |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"}) +        |> json_response_and_validate_schema(:ok) + +      assert response["visibility"] == "unlisted" +    end + +    test "returns 400 when visibility is unknown", %{conn: conn, id: id} do +      conn = +        conn +        |> put_req_header("content-type", "application/json") +        |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "test"}) + +      assert %{"error" => "test - Invalid value for enum."} = +               json_response_and_validate_schema(conn, :bad_request) +    end +  end + +  describe "DELETE /api/pleroma/admin/statuses/:id" do +    setup do +      activity = insert(:note_activity) + +      %{id: activity.id} +    end + +    test "deletes status", %{conn: conn, id: id, admin: admin} do +      conn +      |> delete("/api/pleroma/admin/statuses/#{id}") +      |> json_response_and_validate_schema(:ok) + +      refute Activity.get_by_id(id) + +      log_entry = Repo.one(ModerationLog) + +      assert ModerationLog.get_log_entry_message(log_entry) == +               "@#{admin.nickname} deleted status ##{id}" +    end + +    test "returns 404 when the status does not exist", %{conn: conn} do +      conn = delete(conn, "/api/pleroma/admin/statuses/test") + +      assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"} +    end +  end + +  describe "GET /api/pleroma/admin/statuses" do +    test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do +      blocked = insert(:user) +      user = insert(:user) +      User.block(admin, blocked) + +      {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) + +      {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) +      {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) +      {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) +      {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"}) + +      response = +        conn +        |> get("/api/pleroma/admin/statuses") +        |> json_response_and_validate_schema(200) + +      refute "private" in Enum.map(response, & &1["visibility"]) +      assert length(response) == 3 +    end + +    test "returns only local statuses with local_only on", %{conn: conn} do +      user = insert(:user) +      remote_user = insert(:user, local: false, nickname: "archaeme@archae.me") +      insert(:note_activity, user: user, local: true) +      insert(:note_activity, user: remote_user, local: false) + +      response = +        conn +        |> get("/api/pleroma/admin/statuses?local_only=true") +        |> json_response_and_validate_schema(200) + +      assert length(response) == 1 +    end + +    test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do +      user = insert(:user) + +      {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) + +      {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) +      {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) +      conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") +      assert json_response_and_validate_schema(conn, 200) |> length() == 3 +    end +  end +end | 
