account visibility

This commit is contained in:
Alexander Strizhakov 2020-05-07 13:44:38 +03:00
parent b0ccdb5af4
commit e7bc2f980c
No known key found for this signature in database
GPG key ID: 022896A53AEF1381
6 changed files with 105 additions and 47 deletions

View file

@ -262,37 +262,51 @@ 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) boolean()
| :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: true
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) superuser?(for_user) || visible_account_status(user)
end end
def visible_for?(_, _), do: false def visible_for(_, _), do: false
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)
status in [:active, :password_reset_pending] || status
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,7 +102,9 @@ 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),
404 => Operation.response("Error", "application/json", ApiError) 401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError),
410 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end
@ -142,7 +144,9 @@ 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()),
404 => Operation.response("Error", "application/json", ApiError) 401 => Operation.response("Error", "application/json", ApiError),
404 => Operation.response("Error", "application/json", ApiError),
410 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end

View file

@ -221,17 +221,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 true <- 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 true <- User.visible_for(user, reading_user) do
params = params =
params params
|> Map.delete(:tagged) |> Map.delete(:tagged)
@ -250,7 +250,20 @@ 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
:deactivated ->
render_error(conn, :gone, "")
: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]) == true do
do_render("show.json", opts) do_render("show.json", opts)
else else
%{} %{}

View file

@ -1289,11 +1289,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)
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
@ -1302,14 +1302,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) == true
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)
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
@ -1318,7 +1318,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)
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 401 for deactivated user", %{conn: conn} do
user = insert(:user, deactivated: true)
assert %{} =
conn
|> get("/api/v1/accounts/#{user.id}")
|> json_response_and_validate_schema(:gone)
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 %{} ==
conn
|> get("/api/v1/accounts/#{user.id}/statuses")
|> json_response_and_validate_schema(:gone)
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)
@ -422,15 +449,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
@ -451,10 +478,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
@ -481,10 +508,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