Merge remote-tracking branch 'upstream/develop' into admin-create-users
authorSachin Joshi <satchin.joshi@gmail.com>
Sat, 1 Jun 2019 05:57:37 +0000 (11:42 +0545)
committerSachin Joshi <satchin.joshi@gmail.com>
Sat, 1 Jun 2019 05:57:37 +0000 (11:42 +0545)
1  2 
CHANGELOG.md
lib/pleroma/user.ex
lib/pleroma/web/admin_api/admin_api_controller.ex
lib/pleroma/web/router.ex
test/web/admin_api/admin_api_controller_test.exs

diff --combined CHANGELOG.md
index 5ee853c7698a782b9a9e328faf2801cada205c5c,ff1fff8760bed9d7a8f325b58505f43813652424..8ba48b72cf0d06bb912e9c04f158f9c8bef483b2
@@@ -5,23 -5,32 +5,32 @@@ The format is based on [Keep a Changelo
  
  ## [unreleased]
  ### Added
+ - Optional SSH access mode. (Needs `erlang-ssh` package on some distributions).
+ - [MongooseIM](https://github.com/esl/MongooseIM) http authentication support.
  - LDAP authentication
  - External OAuth provider authentication
  - A [job queue](https://git.pleroma.social/pleroma/pleroma_job_queue) for federation, emails, web push, etc.
  - [Prometheus](https://prometheus.io/) metrics
  - Support for Mastodon's remote interaction
+ - Mix Tasks: `mix pleroma.database bump_all_conversations`
  - Mix Tasks: `mix pleroma.database remove_embedded_objects`
+ - Mix Tasks: `mix pleroma.database update_users_following_followers_counts`
+ - Mix Tasks: `mix pleroma.user toggle_confirmed`
  - Federation: Support for reports
  - Configuration: `safe_dm_mentions` option
  - Configuration: `link_name` option
  - Configuration: `fetch_initial_posts` option
  - Configuration: `notify_email` option
  - Configuration: Media proxy `whitelist` option
+ - Configuration: `report_uri` option
  - Pleroma API: User subscriptions
  - Pleroma API: Healthcheck endpoint
+ - Pleroma API: `/api/v1/pleroma/mascot` per-user frontend mascot configuration endpoints
  - Admin API: Endpoints for listing/revoking invite tokens
  - Admin API: Endpoints for making users follow/unfollow each other
  - Admin API: added filters (role, tags, email, name) for users endpoint
+ - Admin API: Endpoints for managing reports
+ - Admin API: Endpoints for deleting and changing the scope of individual reported statuses
  - AdminFE: initial release with basic user management accessible at /pleroma/admin/
  - Mastodon API: [Scheduled statuses](https://docs.joinmastodon.org/api/rest/scheduled-statuses/)
  - Mastodon API: `/api/v1/notifications/destroy_multiple` (glitch-soc extension)
  - Metadata: RelMe provider
  - OAuth: added support for refresh tokens
  - Emoji packs and emoji pack manager
+ - Object pruning (`mix pleroma.database prune_objects`)
+ - OAuth: added job to clean expired access tokens
+ - MRF: Support for rejecting reports from specific instances (`mrf_simple`)
+ - MRF: Support for stripping avatars and banner images from specific instances (`mrf_simple`)
  
  ### Changed
  - **Breaking:** Configuration: move from Pleroma.Mailer to Pleroma.Emails.Mailer
@@@ -46,7 -59,6 +59,7 @@@
  - Configuration: Added `extra_cookie_attrs` for setting non-standard cookie attributes. Defaults to ["SameSite=Lax"] so that remote follows work.
  - Timelines: Messages involving people you have blocked will be excluded from the timeline in all cases instead of just repeats.
  - Admin API: Move the user related API to `api/pleroma/admin/users`
 +- Admin API: `POST /api/pleroma/admin/users` will take list of users
  - Pleroma API: Support for emoji tags in `/api/pleroma/emoji` resulting in a breaking API change
  - Mastodon API: Support for `exclude_types`, `limit` and `min_id` in `/api/v1/notifications`
  - Mastodon API: Add `languages` and `registrations` to `/api/v1/instance`
@@@ -66,6 -78,9 +79,9 @@@
  - Deps: Updated Ecto to 3.0.7
  - Don't ship finmoji by default, they can be installed as an emoji pack
  - Hide deactivated users and their statuses
+ - Posts which are marked sensitive or tagged nsfw no longer have link previews.
+ - HTTP connection timeout is now set to 10 seconds.
+ - Respond with a 404 Not implemented JSON error message when requested API is not implemented
  
  ### Fixed
  - Added an FTS index on objects. Running `vacuum analyze` and setting a larger `work_mem` is recommended.
  - Mastodon API: Correct `reblogged`, `favourited`, and `bookmarked` values in the reblog status JSON
  - Mastodon API: Exposing default scope of the user to anyone
  - Mastodon API: Make `irreversible` field default to `false` [`POST /api/v1/filters`]
+ - Mastodon API: Replace missing non-nullable Card attributes with empty strings
+ - User-Agent is now sent correctly for all HTTP requests.
+ - MRF: Simple policy now properly delists imported or relayed statuses
  
  ## Removed
- - Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations` 
+ - Configuration: `config :pleroma, :fe` in favor of the more flexible `config :pleroma, :frontend_configurations`
+ ## [0.9.99999] - 2019-05-31
+ ### Security
+ - Mastodon API: Fix lists leaking private posts
  
  ## [0.9.9999] - 2019-04-05
  ### Security
diff --combined lib/pleroma/user.ex
index 722e8ff6b0736d94bf6b364bd47336f7dee3100c,474cd8c1a1b668026271290b253713b321bdaf6b..6abcb7288f5f3237a80cb212409656430cc153ef
@@@ -10,6 -10,7 +10,7 @@@ defmodule Pleroma.User d
  
    alias Comeonin.Pbkdf2
    alias Pleroma.Activity
+   alias Pleroma.Keys
    alias Pleroma.Notification
    alias Pleroma.Object
    alias Pleroma.Registration
@@@ -55,7 -56,7 +56,7 @@@
      field(:last_refreshed_at, :naive_datetime_usec)
      has_many(:notifications, Notification)
      has_many(:registrations, Registration)
-     embeds_one(:info, Pleroma.User.Info)
+     embeds_one(:info, User.Info)
  
      timestamps()
    end
  
    def update_changeset(struct, params \\ %{}) do
      struct
-     |> cast(params, [:bio, :name, :avatar])
+     |> cast(params, [:bio, :name, :avatar, :following])
      |> unique_constraint(:nickname)
      |> validate_format(:nickname, local_nickname_regex())
      |> validate_length(:bio, max: 5000)
        |> validate_confirmation(:password)
        |> unique_constraint(:email)
        |> unique_constraint(:nickname)
-       |> validate_exclusion(:nickname, Pleroma.Config.get([Pleroma.User, :restricted_nicknames]))
+       |> validate_exclusion(:nickname, Pleroma.Config.get([User, :restricted_nicknames]))
        |> validate_format(:nickname, local_nickname_regex())
        |> validate_format(:email, @email_regex)
        |> validate_length(:bio, max: 1000)
    @doc "Inserts provided changeset, performs post-registration actions (confirmation email sending etc.)"
    def register(%Ecto.Changeset{} = changeset) do
      with {:ok, user} <- Repo.insert(changeset),
 -         {:ok, user} <- autofollow_users(user),
 +         {:ok, user} <- post_register_action(user) do
 +      {:ok, user}
 +    end
 +  end
 +
 +  def post_register_action(%User{} = user) do
 +    with {:ok, user} <- autofollow_users(user),
           {:ok, user} <- set_cache(user),
-          {:ok, _} <- Pleroma.User.WelcomeMessage.post_welcome_message_to_user(user),
+          {:ok, _} <- User.WelcomeMessage.post_welcome_message_to_user(user),
           {:ok, _} <- try_send_confirmation_email(user) do
        {:ok, user}
      end
    end
  
    def follow(%User{} = follower, %User{info: info} = followed) do
-     user_config = Application.get_env(:pleroma, :user)
-     deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
+     deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
      ap_followers = followed.follower_address
  
      cond do
      end
    end
  
+   def remove_duplicated_following(%User{following: following} = user) do
+     uniq_following = Enum.uniq(following)
+     if length(following) == length(uniq_following) do
+       {:ok, user}
+     else
+       user
+       |> update_changeset(%{following: uniq_following})
+       |> update_and_set_cache()
+     end
+   end
    @spec get_users_from_set([String.t()], boolean()) :: [User.t()]
    def get_users_from_set(ap_ids, local_only \\ true) do
      criteria = %{ap_id: ap_ids, deactivated: false}
  
      from(s in subquery(boost_search_rank_query(distinct_query, for_user)),
        order_by: [desc: s.search_rank],
-       limit: 20
+       limit: 40
      )
    end
  
      stream =
        ap_id
        |> Activity.query_by_actor()
-       |> Activity.with_preloaded_object()
        |> Repo.stream()
  
      Repo.transaction(fn -> Enum.each(stream, &delete_activity(&1)) end, timeout: :infinity)
    def showing_reblogs?(%User{} = user, %User{} = target) do
      target.ap_id not in user.info.muted_reblogs
    end
+   @spec toggle_confirmation(User.t()) :: {:ok, User.t()} | {:error, Changeset.t()}
+   def toggle_confirmation(%User{} = user) do
+     need_confirmation? = !user.info.confirmation_pending
+     info_changeset =
+       User.Info.confirmation_changeset(user.info, need_confirmation: need_confirmation?)
+     user
+     |> change()
+     |> put_embed(:info, info_changeset)
+     |> update_and_set_cache()
+   end
+   def get_mascot(%{info: %{mascot: %{} = mascot}}) when not is_nil(mascot) do
+     mascot
+   end
+   def get_mascot(%{info: %{mascot: mascot}}) when is_nil(mascot) do
+     # use instance-default
+     config = Pleroma.Config.get([:assets, :mascots])
+     default_mascot = Pleroma.Config.get([:assets, :default_mascot])
+     mascot = Keyword.get(config, default_mascot)
+     %{
+       "id" => "default-mascot",
+       "url" => mascot[:url],
+       "preview_url" => mascot[:url],
+       "pleroma" => %{
+         "mime_type" => mascot[:mime_type]
+       }
+     }
+   end
+   def ensure_keys_present(user) do
+     info = user.info
+     if info.keys do
+       {:ok, user}
+     else
+       {:ok, pem} = Keys.generate_rsa_pem()
+       info_cng =
+         info
+         |> User.Info.set_keys(pem)
+       cng =
+         Ecto.Changeset.change(user)
+         |> Ecto.Changeset.put_embed(:info, info_cng)
+       update_and_set_cache(cng)
+     end
+   end
  end
index 60fd4e57199dc531f8460ce58d9c259ac20a2729,de2a13c015c80d172ca224583d485f47a0819da5..479fd5829087aee42db457af8195e066135d93ac
@@@ -4,11 -4,16 +4,16 @@@
  
  defmodule Pleroma.Web.AdminAPI.AdminAPIController do
    use Pleroma.Web, :controller
+   alias Pleroma.Activity
    alias Pleroma.User
    alias Pleroma.UserInviteToken
+   alias Pleroma.Web.ActivityPub.ActivityPub
    alias Pleroma.Web.ActivityPub.Relay
    alias Pleroma.Web.AdminAPI.AccountView
+   alias Pleroma.Web.AdminAPI.ReportView
    alias Pleroma.Web.AdminAPI.Search
+   alias Pleroma.Web.CommonAPI
+   alias Pleroma.Web.MastodonAPI.StatusView
  
    import Pleroma.Web.ControllerHelper, only: [json_response: 3]
  
      |> json("ok")
    end
  
 -  def user_create(
 -        conn,
 -        %{"nickname" => nickname, "email" => email, "password" => password}
 -      ) do
 -    user_data = %{
 -      nickname: nickname,
 -      name: nickname,
 -      email: email,
 -      password: password,
 -      password_confirmation: password,
 -      bio: "."
 -    }
 -
 -    changeset = User.register_changeset(%User{}, user_data, need_confirmation: false)
 -    {:ok, user} = User.register(changeset)
 -
 -    conn
 -    |> json(user.nickname)
 +  def users_create(conn, %{"users" => users}) do
 +    changesets =
 +      Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
 +        user_data = %{
 +          nickname: nickname,
 +          name: nickname,
 +          email: email,
 +          password: password,
 +          password_confirmation: password,
 +          bio: "."
 +        }
 +
 +        User.register_changeset(%User{}, user_data, need_confirmation: false)
 +      end)
 +      |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
 +        Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
 +      end)
 +
 +    case Pleroma.Repo.transaction(changesets) do
 +      {:ok, users} ->
 +        res =
 +          users
 +          |> Map.values()
 +          |> Enum.map(fn user ->
 +            {:ok, user} = User.post_register_action(user)
 +            user
 +          end)
 +          |> Enum.map(&AccountView.render("created.json", %{user: &1}))
 +
 +        conn
 +        |> json(res)
 +
 +      {:error, id, changeset, _} ->
 +        res =
 +          Enum.map(changesets.operations, fn
 +            {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
 +              AccountView.render("create-error.json", %{changeset: changeset})
 +
 +            {_, {:changeset, current_changeset, _}} ->
 +              AccountView.render("create-error.json", %{changeset: current_changeset})
 +          end)
 +
 +        conn
 +        |> put_status(:conflict)
 +        |> json(res)
 +    end
    end
  
    def user_show(conn, %{"nickname" => nickname}) do
      |> json(token.token)
    end
  
+   def list_reports(conn, params) do
+     params =
+       params
+       |> Map.put("type", "Flag")
+       |> Map.put("skip_preload", true)
+     reports =
+       []
+       |> ActivityPub.fetch_activities(params)
+       |> Enum.reverse()
+     conn
+     |> put_view(ReportView)
+     |> render("index.json", %{reports: reports})
+   end
+   def report_show(conn, %{"id" => id}) do
+     with %Activity{} = report <- Activity.get_by_id(id) do
+       conn
+       |> put_view(ReportView)
+       |> render("show.json", %{report: report})
+     else
+       _ -> {:error, :not_found}
+     end
+   end
+   def report_update_state(conn, %{"id" => id, "state" => state}) do
+     with {:ok, report} <- CommonAPI.update_report_state(id, state) do
+       conn
+       |> put_view(ReportView)
+       |> render("show.json", %{report: report})
+     end
+   end
+   def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
+     with false <- is_nil(params["status"]),
+          %Activity{} <- Activity.get_by_id(id) do
+       params =
+         params
+         |> Map.put("in_reply_to_status_id", id)
+         |> Map.put("visibility", "direct")
+       {:ok, activity} = CommonAPI.post(user, params)
+       conn
+       |> put_view(StatusView)
+       |> render("status.json", %{activity: activity})
+     else
+       true ->
+         {:param_cast, nil}
+       nil ->
+         {:error, :not_found}
+     end
+   end
+   def status_update(conn, %{"id" => id} = params) do
+     with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+       conn
+       |> put_view(StatusView)
+       |> render("status.json", %{activity: activity})
+     end
+   end
+   def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+     with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
+       json(conn, %{})
+     end
+   end
    def errors(conn, {:error, :not_found}) do
      conn
      |> put_status(404)
      |> json("Not found")
    end
  
+   def errors(conn, {:error, reason}) do
+     conn
+     |> put_status(400)
+     |> json(reason)
+   end
    def errors(conn, {:param_cast, _}) do
      conn
      |> put_status(400)
index bbc2fda9bea670f557aa7ff0597c65518cc91af6,352268b967b9938b198ca6afce767d346c07a00a..eb3ee03f30ffda19ab09c7f27e5594bb558a7926
@@@ -156,7 -156,7 +156,7 @@@ defmodule Pleroma.Web.Router d
      post("/user", AdminAPIController, :user_create)
  
      delete("/users", AdminAPIController, :user_delete)
 -    post("/users", AdminAPIController, :user_create)
 +    post("/users", AdminAPIController, :users_create)
      patch("/users/:nickname/toggle_activation", AdminAPIController, :user_toggle_activation)
      put("/users/tag", AdminAPIController, :tag_users)
      delete("/users/tag", AdminAPIController, :untag_users)
  
      get("/users", AdminAPIController, :list_users)
      get("/users/:nickname", AdminAPIController, :user_show)
+     get("/reports", AdminAPIController, :list_reports)
+     get("/reports/:id", AdminAPIController, :report_show)
+     put("/reports/:id", AdminAPIController, :report_update_state)
+     post("/reports/:id/respond", AdminAPIController, :report_respond)
+     put("/statuses/:id", AdminAPIController, :status_update)
+     delete("/statuses/:id", AdminAPIController, :status_delete)
    end
  
    scope "/", Pleroma.Web.TwitterAPI do
  
        post("/pleroma/flavour/:flavour", MastodonAPIController, :set_flavour)
  
+       get("/pleroma/mascot", MastodonAPIController, :get_mascot)
+       put("/pleroma/mascot", MastodonAPIController, :set_mascot)
        post("/reports", MastodonAPIController, :reports)
      end
  
      end
    end
  
+   scope "/", Pleroma.Web.MongooseIM do
+     get("/user_exists", MongooseIMController, :user_exists)
+     get("/check_password", MongooseIMController, :check_password)
+   end
    scope "/", Fallback do
      get("/registration/:token", RedirectController, :registration_page)
      get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
+     get("/api*path", RedirectController, :api_not_implemented)
      get("/*path", RedirectController, :redirector)
  
      options("/*path", RedirectController, :empty)
@@@ -710,6 -727,12 +727,12 @@@ defmodule Fallback.RedirectController d
    alias Pleroma.User
    alias Pleroma.Web.Metadata
  
+   def api_not_implemented(conn, _params) do
+     conn
+     |> put_status(404)
+     |> json(%{error: "Not implemented"})
+   end
    def redirector(conn, _params, code \\ 200) do
      conn
      |> put_resp_content_type("text/html")
index 0199051374cab85b0cb1a7c06f5d384b7b89a5b3,43dcf945a6c93eabf99bcae408434b5cca66b6e8..9721a40342bad8cc9ca71fb41aeb42af59b035c0
@@@ -5,8 -5,10 +5,10 @@@
  defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
    use Pleroma.Web.ConnCase
  
+   alias Pleroma.Activity
    alias Pleroma.User
    alias Pleroma.UserInviteToken
+   alias Pleroma.Web.CommonAPI
    import Pleroma.Factory
  
    describe "/api/pleroma/admin/users" do
          |> assign(:user, admin)
          |> put_req_header("accept", "application/json")
          |> post("/api/pleroma/admin/users", %{
 -          "nickname" => "lain",
 -          "email" => "lain@example.org",
 -          "password" => "test"
 +          "users" => [
 +            %{
 +              "nickname" => "lain",
 +              "email" => "lain@example.org",
 +              "password" => "test"
 +            },
 +            %{
 +              "nickname" => "lain2",
 +              "email" => "lain2@example.org",
 +              "password" => "test"
 +            }
 +          ]
          })
  
 -      assert json_response(conn, 200) == "lain"
 +      assert json_response(conn, 200) == [
 +               %{
 +                 "code" => 200,
 +                 "data" => %{
 +                   "email" => "lain@example.org",
 +                   "nickname" => "lain"
 +                 },
 +                 "type" => "success"
 +               },
 +               %{
 +                 "code" => 200,
 +                 "data" => %{
 +                   "email" => "lain2@example.org",
 +                   "nickname" => "lain2"
 +                 },
 +                 "type" => "success"
 +               }
 +             ]
 +    end
 +
 +    test "Cannot create user with exisiting email" do
 +      admin = insert(:user, info: %{is_admin: true})
 +      user = insert(:user)
 +
 +      conn =
 +        build_conn()
 +        |> assign(:user, admin)
 +        |> 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 exisiting nickname" do
 +      admin = insert(:user, info: %{is_admin: true})
 +      user = insert(:user)
 +
 +      conn =
 +        build_conn()
 +        |> assign(:user, admin)
 +        |> 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" do
 +      admin = insert(:user, info: %{is_admin: true})
 +      user = insert(:user)
 +
 +      conn =
 +        build_conn()
 +        |> assign(:user, admin)
 +        |> 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
  
      end
    end
  
-   test "/api/pleroma/admin/invite_token" do
+   test "/api/pleroma/admin/users/invite_token" do
      admin = insert(:user, info: %{is_admin: true})
  
      conn =
        build_conn()
        |> assign(:user, admin)
        |> put_req_header("accept", "application/json")
-       |> get("/api/pleroma/admin/invite_token")
+       |> get("/api/pleroma/admin/users/invite_token")
  
      assert conn.status == 200
    end
        user = insert(:user, local: false, tags: ["foo", "bar"])
        conn = get(conn, "/api/pleroma/admin/users?page=1")
  
+       users =
+         [
+           %{
+             "deactivated" => admin.info.deactivated,
+             "id" => admin.id,
+             "nickname" => admin.nickname,
+             "roles" => %{"admin" => true, "moderator" => false},
+             "local" => true,
+             "tags" => []
+           },
+           %{
+             "deactivated" => user.info.deactivated,
+             "id" => user.id,
+             "nickname" => user.nickname,
+             "roles" => %{"admin" => false, "moderator" => false},
+             "local" => false,
+             "tags" => ["foo", "bar"]
+           }
+         ]
+         |> Enum.sort_by(& &1["nickname"])
        assert json_response(conn, 200) == %{
                 "count" => 2,
                 "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => admin.info.deactivated,
-                    "id" => admin.id,
-                    "nickname" => admin.nickname,
-                    "roles" => %{"admin" => true, "moderator" => false},
-                    "local" => true,
-                    "tags" => []
-                  },
-                  %{
-                    "deactivated" => user.info.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => false,
-                    "tags" => ["foo", "bar"]
-                  }
-                ]
+                "users" => users
               }
      end
  
          |> assign(:user, admin)
          |> get("/api/pleroma/admin/users?filters=local")
  
+       users =
+         [
+           %{
+             "deactivated" => user.info.deactivated,
+             "id" => user.id,
+             "nickname" => user.nickname,
+             "roles" => %{"admin" => false, "moderator" => false},
+             "local" => true,
+             "tags" => []
+           },
+           %{
+             "deactivated" => admin.info.deactivated,
+             "id" => admin.id,
+             "nickname" => admin.nickname,
+             "roles" => %{"admin" => true, "moderator" => false},
+             "local" => true,
+             "tags" => []
+           },
+           %{
+             "deactivated" => false,
+             "id" => old_admin.id,
+             "local" => true,
+             "nickname" => old_admin.nickname,
+             "roles" => %{"admin" => true, "moderator" => false},
+             "tags" => []
+           }
+         ]
+         |> Enum.sort_by(& &1["nickname"])
        assert json_response(conn, 200) == %{
                 "count" => 3,
                 "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => user.info.deactivated,
-                    "id" => user.id,
-                    "nickname" => user.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => true,
-                    "tags" => []
-                  },
-                  %{
-                    "deactivated" => admin.info.deactivated,
-                    "id" => admin.id,
-                    "nickname" => admin.nickname,
-                    "roles" => %{"admin" => true, "moderator" => false},
-                    "local" => true,
-                    "tags" => []
-                  },
-                  %{
-                    "deactivated" => false,
-                    "id" => old_admin.id,
-                    "local" => true,
-                    "nickname" => old_admin.nickname,
-                    "roles" => %{"admin" => true, "moderator" => false},
-                    "tags" => []
-                  }
-                ]
+                "users" => users
               }
      end
  
  
        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" => []
+           },
+           %{
+             "deactivated" => false,
+             "id" => second_admin.id,
+             "nickname" => second_admin.nickname,
+             "roles" => %{"admin" => true, "moderator" => false},
+             "local" => second_admin.local,
+             "tags" => []
+           }
+         ]
+         |> Enum.sort_by(& &1["nickname"])
        assert json_response(conn, 200) == %{
                 "count" => 2,
                 "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => false,
-                    "id" => admin.id,
-                    "nickname" => admin.nickname,
-                    "roles" => %{"admin" => true, "moderator" => false},
-                    "local" => admin.local,
-                    "tags" => []
-                  },
-                  %{
-                    "deactivated" => false,
-                    "id" => second_admin.id,
-                    "nickname" => second_admin.nickname,
-                    "roles" => %{"admin" => true, "moderator" => false},
-                    "local" => second_admin.local,
-                    "tags" => []
-                  }
-                ]
+                "users" => users
               }
      end
  
  
        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"]
+           },
+           %{
+             "deactivated" => false,
+             "id" => user2.id,
+             "nickname" => user2.nickname,
+             "roles" => %{"admin" => false, "moderator" => false},
+             "local" => user2.local,
+             "tags" => ["second"]
+           }
+         ]
+         |> Enum.sort_by(& &1["nickname"])
        assert json_response(conn, 200) == %{
                 "count" => 2,
                 "page_size" => 50,
-                "users" => [
-                  %{
-                    "deactivated" => false,
-                    "id" => user1.id,
-                    "nickname" => user1.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => user1.local,
-                    "tags" => ["first"]
-                  },
-                  %{
-                    "deactivated" => false,
-                    "id" => user2.id,
-                    "nickname" => user2.nickname,
-                    "roles" => %{"admin" => false, "moderator" => false},
-                    "local" => user2.local,
-                    "tags" => ["second"]
-                  }
-                ]
+                "users" => users
               }
      end
  
               }
      end
    end
+   describe "GET /api/pleroma/admin/reports/:id" do
+     setup %{conn: conn} do
+       admin = insert(:user, info: %{is_admin: true})
+       %{conn: assign(conn, :user, admin)}
+     end
+     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(: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(conn, :not_found) == "Not found"
+     end
+   end
+   describe "PUT /api/pleroma/admin/reports/:id" do
+     setup %{conn: conn} do
+       admin = insert(:user, info: %{is_admin: true})
+       [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: assign(conn, :user, admin), id: report_id}
+     end
+     test "mark report as resolved", %{conn: conn, id: id} do
+       response =
+         conn
+         |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "resolved"})
+         |> json_response(:ok)
+       assert response["state"] == "resolved"
+     end
+     test "closes report", %{conn: conn, id: id} do
+       response =
+         conn
+         |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "closed"})
+         |> json_response(:ok)
+       assert response["state"] == "closed"
+     end
+     test "returns 400 when state is unknown", %{conn: conn, id: id} do
+       conn =
+         conn
+         |> put("/api/pleroma/admin/reports/#{id}", %{"state" => "test"})
+       assert json_response(conn, :bad_request) == "Unsupported state"
+     end
+     test "returns 404 when report is not exist", %{conn: conn} do
+       conn =
+         conn
+         |> put("/api/pleroma/admin/reports/test", %{"state" => "closed"})
+       assert json_response(conn, :not_found) == "Not found"
+     end
+   end
+   describe "GET /api/pleroma/admin/reports" do
+     setup %{conn: conn} do
+       admin = insert(:user, info: %{is_admin: true})
+       %{conn: assign(conn, :user, admin)}
+     end
+     test "returns empty response when no reports created", %{conn: conn} do
+       response =
+         conn
+         |> get("/api/pleroma/admin/reports")
+         |> json_response(:ok)
+       assert Enum.empty?(response["reports"])
+     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("/api/pleroma/admin/reports")
+         |> json_response(:ok)
+       [report] = response["reports"]
+       assert length(response["reports"]) == 1
+       assert report["id"] == report_id
+     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("/api/pleroma/admin/reports", %{
+           "state" => "open"
+         })
+         |> json_response(:ok)
+       [open_report] = response["reports"]
+       assert length(response["reports"]) == 1
+       assert open_report["id"] == first_report_id
+       response =
+         conn
+         |> get("/api/pleroma/admin/reports", %{
+           "state" => "closed"
+         })
+         |> json_response(:ok)
+       [closed_report] = response["reports"]
+       assert length(response["reports"]) == 1
+       assert closed_report["id"] == second_report_id
+       response =
+         conn
+         |> get("/api/pleroma/admin/reports", %{
+           "state" => "resolved"
+         })
+         |> json_response(:ok)
+       assert Enum.empty?(response["reports"])
+     end
+     test "returns 403 when requested by a non-admin" do
+       user = insert(:user)
+       conn =
+         build_conn()
+         |> assign(:user, user)
+         |> get("/api/pleroma/admin/reports")
+       assert json_response(conn, :forbidden) == %{"error" => "User is not admin."}
+     end
+     test "returns 403 when requested by anonymous" do
+       conn =
+         build_conn()
+         |> get("/api/pleroma/admin/reports")
+       assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+     end
+   end
+   describe "POST /api/pleroma/admin/reports/:id/respond" do
+     setup %{conn: conn} do
+       admin = insert(:user, info: %{is_admin: true})
+       %{conn: assign(conn, :user, admin)}
+     end
+     test "returns created dm", %{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
+         |> post("/api/pleroma/admin/reports/#{report_id}/respond", %{
+           "status" => "I will check it out"
+         })
+         |> json_response(:ok)
+       recipients = Enum.map(response["mentions"], & &1["username"])
+       assert conn.assigns[:user].nickname in recipients
+       assert reporter.nickname in recipients
+       assert response["content"] == "I will check it out"
+       assert response["visibility"] == "direct"
+     end
+     test "returns 400 when status is missing", %{conn: conn} do
+       conn = post(conn, "/api/pleroma/admin/reports/test/respond")
+       assert json_response(conn, :bad_request) == "Invalid parameters"
+     end
+     test "returns 404 when report id is invalid", %{conn: conn} do
+       conn =
+         post(conn, "/api/pleroma/admin/reports/test/respond", %{
+           "status" => "foo"
+         })
+       assert json_response(conn, :not_found) == "Not found"
+     end
+   end
+   describe "PUT /api/pleroma/admin/statuses/:id" do
+     setup %{conn: conn} do
+       admin = insert(:user, info: %{is_admin: true})
+       activity = insert(:note_activity)
+       %{conn: assign(conn, :user, admin), id: activity.id}
+     end
+     test "toggle sensitive flag", %{conn: conn, id: id} do
+       response =
+         conn
+         |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
+         |> json_response(:ok)
+       assert response["sensitive"]
+       response =
+         conn
+         |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
+         |> json_response(:ok)
+       refute response["sensitive"]
+     end
+     test "change visibility flag", %{conn: conn, id: id} do
+       response =
+         conn
+         |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "public"})
+         |> json_response(:ok)
+       assert response["visibility"] == "public"
+       response =
+         conn
+         |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "private"})
+         |> json_response(:ok)
+       assert response["visibility"] == "private"
+       response =
+         conn
+         |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "unlisted"})
+         |> json_response(:ok)
+       assert response["visibility"] == "unlisted"
+     end
+     test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
+       conn =
+         conn
+         |> put("/api/pleroma/admin/statuses/#{id}", %{"visibility" => "test"})
+       assert json_response(conn, :bad_request) == "Unsupported visibility"
+     end
+   end
+   describe "DELETE /api/pleroma/admin/statuses/:id" do
+     setup %{conn: conn} do
+       admin = insert(:user, info: %{is_admin: true})
+       activity = insert(:note_activity)
+       %{conn: assign(conn, :user, admin), id: activity.id}
+     end
+     test "deletes status", %{conn: conn, id: id} do
+       conn
+       |> delete("/api/pleroma/admin/statuses/#{id}")
+       |> json_response(:ok)
+       refute Activity.get_by_id(id)
+     end
+     test "returns error when status is not exist", %{conn: conn} do
+       conn =
+         conn
+         |> delete("/api/pleroma/admin/statuses/test")
+       assert json_response(conn, :bad_request) == "Could not delete"
+     end
+   end
  end