Merge branch 'feature/1739-account-endpoints' into 'develop'

account visibility in masto api

Closes #1739

See merge request pleroma/pleroma!2488
This commit is contained in:
lain 2020-06-22 12:37:10 +00:00
commit 59bdef0c33
6 changed files with 107 additions and 45 deletions

View file

@ -263,37 +263,60 @@ def account_status(%User{deactivated: true}), do: :deactivated
def account_status(%User{password_reset_pending: true}), do: :password_reset_pending def account_status(%User{password_reset_pending: true}), do: :password_reset_pending
def account_status(%User{confirmation_pending: true}) do def account_status(%User{confirmation_pending: true}) do
case Config.get([:instance, :account_activation_required]) do if Config.get([:instance, :account_activation_required]) do
true -> :confirmation_pending :confirmation_pending
_ -> :active else
:active
end end
end end
def account_status(%User{}), do: :active def account_status(%User{}), do: :active
@spec visible_for?(User.t(), User.t() | nil) :: boolean() @spec visible_for(User.t(), User.t() | nil) ::
def visible_for?(user, for_user \\ nil) :visible
| :invisible
| :restricted_unauthenticated
| :deactivated
| :confirmation_pending
def visible_for(user, for_user \\ nil)
def visible_for?(%User{invisible: true}, _), do: false def visible_for(%User{invisible: true}, _), do: :invisible
def visible_for?(%User{id: user_id}, %User{id: user_id}), do: true def visible_for(%User{id: user_id}, %User{id: user_id}), do: :visible
def visible_for?(%User{local: local} = user, nil) do def visible_for(%User{} = user, nil) do
cfg_key = if restrict_unauthenticated?(user) do
if local, :restrict_unauthenticated
do: :local, else
else: :remote visible_account_status(user)
end
if Config.get([:restrict_unauthenticated, :profiles, cfg_key]),
do: false,
else: account_status(user) == :active
end end
def visible_for?(%User{} = user, for_user) do def visible_for(%User{} = user, for_user) do
account_status(user) == :active || superuser?(for_user) if superuser?(for_user) do
:visible
else
visible_account_status(user)
end
end end
def visible_for?(_, _), do: false def visible_for(_, _), do: :invisible
defp restrict_unauthenticated?(%User{local: local}) do
config_key = if local, do: :local, else: :remote
Config.get([:restrict_unauthenticated, :profiles, config_key], false)
end
defp visible_account_status(user) do
status = account_status(user)
if status in [:active, :password_reset_pending] do
:visible
else
status
end
end
@spec superuser?(User.t()) :: boolean() @spec superuser?(User.t()) :: boolean()
def superuser?(%User{local: true, is_admin: true}), do: true def superuser?(%User{local: true, is_admin: true}), do: true

View file

@ -102,6 +102,7 @@ def show_operation do
parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}], parameters: [%Reference{"$ref": "#/components/parameters/accountIdOrNickname"}],
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", Account), 200 => Operation.response("Account", "application/json", Account),
401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError) 404 => Operation.response("Error", "application/json", ApiError)
} }
} }
@ -142,6 +143,7 @@ def statuses_operation do
] ++ pagination_params(), ] ++ pagination_params(),
responses: %{ responses: %{
200 => Operation.response("Statuses", "application/json", array_of_statuses()), 200 => Operation.response("Statuses", "application/json", array_of_statuses()),
401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError) 404 => Operation.response("Error", "application/json", ApiError)
} }
} }

View file

@ -234,17 +234,17 @@ def relationships(%{assigns: %{user: _user}} = conn, _), do: json(conn, [])
@doc "GET /api/v1/accounts/:id" @doc "GET /api/v1/accounts/:id"
def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do def show(%{assigns: %{user: for_user}} = conn, %{id: nickname_or_id}) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user), with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),
true <- User.visible_for?(user, for_user) do :visible <- User.visible_for(user, for_user) do
render(conn, "show.json", user: user, for: for_user) render(conn, "show.json", user: user, for: for_user)
else else
_e -> render_error(conn, :not_found, "Can't find user") error -> user_visibility_error(conn, error)
end end
end end
@doc "GET /api/v1/accounts/:id/statuses" @doc "GET /api/v1/accounts/:id/statuses"
def statuses(%{assigns: %{user: reading_user}} = conn, params) do def statuses(%{assigns: %{user: reading_user}} = conn, params) do
with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user), with %User{} = user <- User.get_cached_by_nickname_or_id(params.id, for: reading_user),
true <- User.visible_for?(user, reading_user) do :visible <- User.visible_for(user, reading_user) do
params = params =
params params
|> Map.delete(:tagged) |> Map.delete(:tagged)
@ -261,7 +261,17 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do
as: :activity as: :activity
) )
else else
_e -> render_error(conn, :not_found, "Can't find user") error -> user_visibility_error(conn, error)
end
end
defp user_visibility_error(conn, error) do
case error do
:restrict_unauthenticated ->
render_error(conn, :unauthorized, "This API requires an authenticated user")
_ ->
render_error(conn, :not_found, "Can't find user")
end end
end end

View file

@ -35,7 +35,7 @@ def render("index.json", %{users: users} = opts) do
end end
def render("show.json", %{user: user} = opts) do def render("show.json", %{user: user} = opts) do
if User.visible_for?(user, opts[:for]) do if User.visible_for(user, opts[:for]) == :visible do
do_render("show.json", opts) do_render("show.json", opts)
else else
%{} %{}

View file

@ -1342,11 +1342,11 @@ test "returns false for a non-invisible user" do
end end
end end
describe "visible_for?/2" do describe "visible_for/2" do
test "returns true when the account is itself" do test "returns true when the account is itself" do
user = insert(:user, local: true) user = insert(:user, local: true)
assert User.visible_for?(user, user) assert User.visible_for(user, user) == :visible
end end
test "returns false when the account is unauthenticated and auth is required" do test "returns false when the account is unauthenticated and auth is required" do
@ -1355,14 +1355,14 @@ test "returns false when the account is unauthenticated and auth is required" do
user = insert(:user, local: true, confirmation_pending: true) user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true) other_user = insert(:user, local: true)
refute User.visible_for?(user, other_user) refute User.visible_for(user, other_user) == :visible
end end
test "returns true when the account is unauthenticated and auth is not required" do test "returns true when the account is unauthenticated and auth is not required" do
user = insert(:user, local: true, confirmation_pending: true) user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true) other_user = insert(:user, local: true)
assert User.visible_for?(user, other_user) assert User.visible_for(user, other_user) == :visible
end end
test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do test "returns true when the account is unauthenticated and being viewed by a privileged account (auth required)" do
@ -1371,7 +1371,7 @@ test "returns true when the account is unauthenticated and being viewed by a pri
user = insert(:user, local: true, confirmation_pending: true) user = insert(:user, local: true, confirmation_pending: true)
other_user = insert(:user, local: true, is_admin: true) other_user = insert(:user, local: true, is_admin: true)
assert User.visible_for?(user, other_user) assert User.visible_for(user, other_user) == :visible
end end
end end

View file

@ -127,6 +127,15 @@ test "returns 404 for internal.fetch actor", %{conn: conn} do
|> get("/api/v1/accounts/internal.fetch") |> get("/api/v1/accounts/internal.fetch")
|> json_response_and_validate_schema(404) |> json_response_and_validate_schema(404)
end end
test "returns 404 for deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(:not_found)
end
end end
defp local_and_remote_users do defp local_and_remote_users do
@ -143,15 +152,15 @@ defp local_and_remote_users do
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{local.id}") |> get("/api/v1/accounts/#{local.id}")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{remote.id}") |> get("/api/v1/accounts/#{remote.id}")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
end end
test "if user is authenticated", %{local: local, remote: remote} do test "if user is authenticated", %{local: local, remote: remote} do
@ -173,8 +182,8 @@ test "if user is authenticated", %{local: local, remote: remote} do
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
res_conn = get(conn, "/api/v1/accounts/#{local.id}") res_conn = get(conn, "/api/v1/accounts/#{local.id}")
assert json_response_and_validate_schema(res_conn, :not_found) == %{ assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
"error" => "Can't find user" "error" => "This API requires an authenticated user"
} }
res_conn = get(conn, "/api/v1/accounts/#{remote.id}") res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
@ -203,8 +212,8 @@ test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} d
res_conn = get(conn, "/api/v1/accounts/#{remote.id}") res_conn = get(conn, "/api/v1/accounts/#{remote.id}")
assert json_response_and_validate_schema(res_conn, :not_found) == %{ assert json_response_and_validate_schema(res_conn, :unauthorized) == %{
"error" => "Can't find user" "error" => "This API requires an authenticated user"
} }
end end
@ -249,6 +258,24 @@ test "works with announces that are just addressed to public", %{conn: conn} do
assert id == announce.id assert id == announce.id
end end
test "deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{"error" => "Can't find user"} ==
conn
|> get("/api/v1/accounts/#{user.id}/statuses")
|> json_response_and_validate_schema(:not_found)
end
test "returns 404 when user is invisible", %{conn: conn} do
user = insert(:user, %{invisible: true})
assert %{"error" => "Can't find user"} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(404)
end
test "respects blocks", %{user: user_one, conn: conn} do test "respects blocks", %{user: user_one, conn: conn} do
user_two = insert(:user) user_two = insert(:user)
user_three = insert(:user) user_three = insert(:user)
@ -430,15 +457,15 @@ defp local_and_remote_activities(%{local: local, remote: remote}) do
setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :remote], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{local.id}/statuses") |> get("/api/v1/accounts/#{local.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{remote.id}/statuses") |> get("/api/v1/accounts/#{remote.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
end end
test "if user is authenticated", %{local: local, remote: remote} do test "if user is authenticated", %{local: local, remote: remote} do
@ -459,10 +486,10 @@ test "if user is authenticated", %{local: local, remote: remote} do
setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true) setup do: clear_config([:restrict_unauthenticated, :profiles, :local], true)
test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{local.id}/statuses") |> get("/api/v1/accounts/#{local.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
@ -489,10 +516,10 @@ test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} d
res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses")
assert length(json_response_and_validate_schema(res_conn, 200)) == 1 assert length(json_response_and_validate_schema(res_conn, 200)) == 1
assert %{"error" => "Can't find user"} == assert %{"error" => "This API requires an authenticated user"} ==
conn conn
|> get("/api/v1/accounts/#{remote.id}/statuses") |> get("/api/v1/accounts/#{remote.id}/statuses")
|> json_response_and_validate_schema(:not_found) |> json_response_and_validate_schema(:unauthorized)
end end
test "if user is authenticated", %{local: local, remote: remote} do test "if user is authenticated", %{local: local, remote: remote} do