From fe15f0ba15d02809fa4c21fb646e65d06060f3bb Mon Sep 17 00:00:00 2001 From: Alexander Strizhakov Date: Fri, 20 Mar 2020 13:04:37 +0300 Subject: [PATCH] restrict_unauthenticated setting --- CHANGELOG.md | 1 + config/config.exs | 5 + config/description.exs | 60 +++++ docs/configuration/cheatsheet.md | 18 ++ lib/pleroma/user.ex | 13 +- lib/pleroma/web/activity_pub/visibility.ex | 14 +- .../controllers/account_controller.ex | 7 +- .../controllers/status_controller.ex | 2 +- .../controllers/timeline_controller.ex | 35 ++- .../controllers/account_controller_test.exs | 213 +++++++++++++++++- .../controllers/status_controller_test.exs | 169 ++++++++++++++ .../controllers/timeline_controller_test.exs | 111 ++++++++- 12 files changed, 615 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a27200895..15a073c64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list. +- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
API Changes - Mastodon API: Support for `include_types` in `/api/v1/notifications`. diff --git a/config/config.exs b/config/config.exs index 3357e23e7..2ab939107 100644 --- a/config/config.exs +++ b/config/config.exs @@ -624,6 +624,11 @@ parameters: [gin_fuzzy_search_limit: "500"], prepare: :unnamed +config :pleroma, :restrict_unauthenticated, + timelines: %{local: false, federated: false}, + profiles: %{local: false, remote: false}, + activities: %{local: false, remote: false} + # Import environment specific config. This must remain at the bottom # of this file so it overrides the configuration defined above. import_config "#{Mix.env()}.exs" diff --git a/config/description.exs b/config/description.exs index 732c76734..3781fb9cb 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2915,5 +2915,65 @@ suggestions: [2] } ] + }, + %{ + group: :pleroma, + key: :restrict_unauthenticated, + type: :group, + description: + "Disallow viewing timelines, user profiles and statuses for unauthenticated users.", + children: [ + %{ + key: :timelines, + type: :map, + description: "Settings for public and federated timelines.", + children: [ + %{ + key: :local, + type: :boolean, + description: "Disallow view public timeline." + }, + %{ + key: :federated, + type: :boolean, + description: "Disallow view federated timeline." + } + ] + }, + %{ + key: :profiles, + type: :map, + description: "Settings for user profiles.", + children: [ + %{ + key: :local, + type: :boolean, + description: "Disallow view local user profiles." + }, + %{ + key: :remote, + type: :boolean, + description: "Disallow view remote user profiles." + } + ] + }, + %{ + key: :activities, + type: :map, + description: "Settings for statuses.", + children: [ + %{ + key: :local, + type: :boolean, + description: "Disallow view local statuses." + }, + %{ + key: :remote, + type: :boolean, + description: "Disallow view remote statuses." + } + ] + } + ] } ] diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 4012fe9b1..d16435e11 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -872,3 +872,21 @@ config :auto_linker, ## :configurable_from_database Boolean, enables/disables in-database configuration. Read [Transfering the config to/from the database](../administration/CLI_tasks/config.md) for more information. + + + +## Restrict entities access for unauthenticated users + +### :restrict_unauthenticated + +Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses. + +* `timelines` - public and federated timelines + * `local` - public timeline + * `federated` +* `profiles` - user profiles + * `local` + * `remote` +* `activities` - statuses + * `local` + * `remote` \ No newline at end of file diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 911dde6e2..8693c0b80 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -237,7 +237,18 @@ def visible_for?(user, for_user \\ nil) def visible_for?(%User{invisible: true}, _), do: false - def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_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 + cfg_key = + if local, + do: :local, + else: :remote + + if Config.get([:restrict_unauthenticated, :profiles, cfg_key]), + do: false, + else: account_status(user) == :active + end def visible_for?(%User{} = user, for_user) do account_status(user) == :active || superuser?(for_user) diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 6f226fc92..453a6842e 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -44,6 +44,7 @@ def is_direct?(activity) do def is_list?(%{data: %{"listMessage" => _}}), do: true def is_list?(_), do: false + @spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean() def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do @@ -55,14 +56,21 @@ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{ def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false - def visible_for_user?(activity, nil) do - is_public?(activity) + def visible_for_user?(%{local: local} = activity, nil) do + cfg_key = + if local, + do: :local, + else: :remote + + if Pleroma.Config.get([:restrict_unauthenticated, :activities, cfg_key]), + do: false, + else: is_public?(activity) end def visible_for_user?(activity, user) do x = [user.ap_id | User.following(user)] y = [activity.actor] ++ activity.data["to"] ++ (activity.data["cc"] || []) - visible_for_user?(activity, nil) || Enum.any?(x, &(&1 in y)) + is_public?(activity) || Enum.any?(x, &(&1 in y)) end def entire_thread_visible_for_user?(%Activity{} = activity, %User{} = user) do diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 88c997b9f..6dbf11ac9 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -60,7 +60,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug - when action != :create + when action not in [:create, :show, :statuses] ) @relations [:follow, :unfollow] @@ -259,7 +259,8 @@ def show(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do @doc "GET /api/v1/accounts/:id/statuses" def statuses(%{assigns: %{user: reading_user}} = conn, params) do - with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do + with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user), + true <- User.visible_for?(user, reading_user) do params = params |> Map.put("tag", params["tagged"]) @@ -271,6 +272,8 @@ def statuses(%{assigns: %{user: reading_user}} = conn, params) do |> add_link_headers(activities) |> put_view(StatusView) |> render("index.json", activities: activities, for: reading_user, as: :activity) + else + _e -> render_error(conn, :not_found, "Can't find user") end end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 5c90065f6..37afe6949 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -76,7 +76,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] ) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action not in [:index, :show]) @rate_limited_status_actions ~w(reblog unreblog favourite unfavourite create delete)a diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 09e08271b..91f41416d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -27,7 +27,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) + plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug when action != :public) plug(:put_view, Pleroma.Web.MastodonAPI.StatusView) @@ -75,17 +75,30 @@ def direct(%{assigns: %{user: user}} = conn, params) do def public(%{assigns: %{user: user}} = conn, params) do local_only = truthy_param?(params["local"]) - activities = - params - |> Map.put("type", ["Create", "Announce"]) - |> Map.put("local_only", local_only) - |> Map.put("blocking_user", user) - |> Map.put("muting_user", user) - |> ActivityPub.fetch_public_activities() + cfg_key = + if local_only do + :local + else + :federated + end - conn - |> add_link_headers(activities, %{"local" => local_only}) - |> render("index.json", activities: activities, for: user, as: :activity) + restrict? = Pleroma.Config.get([:restrict_unauthenticated, :timelines, cfg_key]) + + if not (restrict? and is_nil(user)) do + activities = + params + |> Map.put("type", ["Create", "Announce"]) + |> Map.put("local_only", local_only) + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> ActivityPub.fetch_public_activities() + + conn + |> add_link_headers(activities, %{"local" => local_only}) + |> render("index.json", activities: activities, for: user, as: :activity) + else + render_error(conn, :unauthorized, "authorization required for timeline view") + end end def hashtag_fetching(params, user, local_only) do diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 7efccd9c4..2182dd28e 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -5,6 +5,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do use Pleroma.Web.ConnCase + alias Pleroma.Config alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub @@ -46,7 +47,7 @@ test "works by nickname" do end test "works by nickname for remote users" do - Pleroma.Config.put([:instance, :limit_to_local_content], false) + Config.put([:instance, :limit_to_local_content], false) user = insert(:user, nickname: "user@example.com", local: false) conn = @@ -58,7 +59,7 @@ test "works by nickname for remote users" do end test "respects limit_to_local_content == :all for remote user nicknames" do - Pleroma.Config.put([:instance, :limit_to_local_content], :all) + Config.put([:instance, :limit_to_local_content], :all) user = insert(:user, nickname: "user@example.com", local: false) @@ -70,7 +71,7 @@ test "respects limit_to_local_content == :all for remote user nicknames" do end test "respects limit_to_local_content == :unauthenticated for remote user nicknames" do - Pleroma.Config.put([:instance, :limit_to_local_content], :unauthenticated) + Config.put([:instance, :limit_to_local_content], :unauthenticated) user = insert(:user, nickname: "user@example.com", local: false) reading_user = insert(:user) @@ -140,6 +141,106 @@ test "returns 404 for internal.fetch actor", %{conn: conn} do end end + defp local_and_remote_users do + local = insert(:user) + remote = insert(:user, local: false) + {:ok, local: local, remote: remote} + end + + describe "user fetching with restrict unauthenticated profiles for local and remote" do + setup do: local_and_remote_users() + + clear_config([:restrict_unauthenticated, :profiles, :local]) do + Config.put([:restrict_unauthenticated, :profiles, :local], true) + end + + clear_config([:restrict_unauthenticated, :profiles, :remote]) do + Config.put([:restrict_unauthenticated, :profiles, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/accounts/#{local.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/accounts/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + end + + describe "user fetching with restrict unauthenticated profiles for local" do + setup do: local_and_remote_users() + + clear_config([:restrict_unauthenticated, :profiles, :local]) do + Config.put([:restrict_unauthenticated, :profiles, :local], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/accounts/#{local.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/accounts/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + end + + describe "user fetching with restrict unauthenticated profiles for remote" do + setup do: local_and_remote_users() + + clear_config([:restrict_unauthenticated, :profiles, :remote]) do + Config.put([:restrict_unauthenticated, :profiles, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/accounts/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/accounts/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + end + describe "user timelines" do setup do: oauth_access(["read:statuses"]) @@ -293,6 +394,110 @@ test "the user views their own timelines and excludes direct messages", %{ end end + defp local_and_remote_activities(%{local: local, remote: remote}) do + insert(:note_activity, user: local) + insert(:note_activity, user: remote, local: false) + + :ok + end + + describe "statuses with restrict unauthenticated profiles for local and remote" do + setup do: local_and_remote_users() + setup :local_and_remote_activities + + clear_config([:restrict_unauthenticated, :profiles, :local]) do + Config.put([:restrict_unauthenticated, :profiles, :local], true) + end + + clear_config([:restrict_unauthenticated, :profiles, :remote]) do + Config.put([:restrict_unauthenticated, :profiles, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + end + end + + describe "statuses with restrict unauthenticated profiles for local" do + setup do: local_and_remote_users() + setup :local_and_remote_activities + + clear_config([:restrict_unauthenticated, :profiles, :local]) do + Config.put([:restrict_unauthenticated, :profiles, :local], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + end + end + + describe "statuses with restrict unauthenticated profiles for remote" do + setup do: local_and_remote_users() + setup :local_and_remote_activities + + clear_config([:restrict_unauthenticated, :profiles, :remote]) do + Config.put([:restrict_unauthenticated, :profiles, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Can't find user" + } + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/accounts/#{local.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/accounts/#{remote.id}/statuses") + assert length(json_response(res_conn, 200)) == 1 + end + end + describe "followers" do setup do: oauth_access(["read:accounts"]) @@ -757,7 +962,7 @@ test "returns forbidden if token is invalid", %{conn: conn, valid_params: valid_ describe "create account by app / rate limit" do clear_config([:rate_limit, :app_account_creation]) do - Pleroma.Config.put([:rate_limit, :app_account_creation], {10_000, 2}) + Config.put([:rate_limit, :app_account_creation], {10_000, 2}) end test "respects rate limit setting", %{conn: conn} do diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index fbf63f608..81513a429 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -476,6 +476,103 @@ test "get a status" do assert id == to_string(activity.id) end + defp local_and_remote_activities do + local = insert(:note_activity) + remote = insert(:note_activity, local: false) + {:ok, local: local, remote: remote} + end + + describe "status with restrict unauthenticated activities for local and remote" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :activities, :local]) do + Config.put([:restrict_unauthenticated, :activities, :local], true) + end + + clear_config([:restrict_unauthenticated, :activities, :remote]) do + Config.put([:restrict_unauthenticated, :activities, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/statuses/#{local.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Record not found" + } + + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Record not found" + } + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + end + + describe "status with restrict unauthenticated activities for local" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :activities, :local]) do + Config.put([:restrict_unauthenticated, :activities, :local], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/statuses/#{local.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Record not found" + } + + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + end + + describe "status with restrict unauthenticated activities for remote" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :activities, :remote]) do + Config.put([:restrict_unauthenticated, :activities, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/statuses/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") + + assert json_response(res_conn, :not_found) == %{ + "error" => "Record not found" + } + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + res_conn = get(conn, "/api/v1/statuses/#{local.id}") + assert %{"id" => _} = json_response(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") + assert %{"id" => _} = json_response(res_conn, 200) + end + end + test "getting a status that doesn't exist returns 404" do %{conn: conn} = oauth_access(["read:statuses"]) activity = insert(:note_activity) @@ -514,6 +611,78 @@ test "get statuses by IDs" do assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response(conn, :ok), & &1["id"]) end + describe "getting statuses by ids with restricted unauthenticated for local and remote" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :activities, :local]) do + Config.put([:restrict_unauthenticated, :activities, :local], true) + end + + clear_config([:restrict_unauthenticated, :activities, :remote]) do + Config.put([:restrict_unauthenticated, :activities, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/statuses", %{ids: [local.id, remote.id]}) + + assert json_response(res_conn, 200) == [] + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/statuses", %{ids: [local.id, remote.id]}) + + assert length(json_response(res_conn, 200)) == 2 + end + end + + describe "getting statuses by ids with restricted unauthenticated for local" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :activities, :local]) do + Config.put([:restrict_unauthenticated, :activities, :local], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/statuses", %{ids: [local.id, remote.id]}) + + remote_id = remote.id + assert [%{"id" => ^remote_id}] = json_response(res_conn, 200) + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/statuses", %{ids: [local.id, remote.id]}) + + assert length(json_response(res_conn, 200)) == 2 + end + end + + describe "getting statuses by ids with restricted unauthenticated for remote" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :activities, :remote]) do + Config.put([:restrict_unauthenticated, :activities, :remote], true) + end + + test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + res_conn = get(conn, "/api/v1/statuses", %{ids: [local.id, remote.id]}) + + local_id = local.id + assert [%{"id" => ^local_id}] = json_response(res_conn, 200) + end + + test "if user is authenticated", %{local: local, remote: remote} do + %{conn: conn} = oauth_access(["read"]) + + res_conn = get(conn, "/api/v1/statuses", %{ids: [local.id, remote.id]}) + + assert length(json_response(res_conn, 200)) == 2 + end + end + describe "deleting a status" do test "when you created it" do %{user: author, conn: conn} = oauth_access(["write:statuses"]) diff --git a/test/web/mastodon_api/controllers/timeline_controller_test.exs b/test/web/mastodon_api/controllers/timeline_controller_test.exs index 2c03b0a75..a15c759d4 100644 --- a/test/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/web/mastodon_api/controllers/timeline_controller_test.exs @@ -12,8 +12,6 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do alias Pleroma.User alias Pleroma.Web.CommonAPI - clear_config([:instance, :public]) - setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) :ok @@ -80,15 +78,6 @@ test "the public timeline", %{conn: conn} do assert [%{"content" => "test"}] = json_response(conn, :ok) end - test "the public timeline when public is set to false", %{conn: conn} do - Config.put([:instance, :public], false) - - assert %{"error" => "This resource requires authentication."} == - conn - |> get("/api/v1/timelines/public", %{"local" => "False"}) - |> json_response(:forbidden) - end - test "the public timeline includes only public statuses for an authenticated user" do %{user: user, conn: conn} = oauth_access(["read:statuses"]) @@ -102,6 +91,106 @@ test "the public timeline includes only public statuses for an authenticated use end end + defp local_and_remote_activities do + insert(:note_activity) + insert(:note_activity, local: false) + :ok + end + + describe "public with restrict unauthenticated timeline for local and federated timelines" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :timelines, :local]) do + Config.put([:restrict_unauthenticated, :timelines, :local], true) + end + + clear_config([:restrict_unauthenticated, :timelines, :federated]) do + Config.put([:restrict_unauthenticated, :timelines, :federated], true) + end + + test "if user is unauthenticated", %{conn: conn} do + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"}) + + assert json_response(res_conn, :unauthorized) == %{ + "error" => "authorization required for timeline view" + } + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"}) + + assert json_response(res_conn, :unauthorized) == %{ + "error" => "authorization required for timeline view" + } + end + + test "if user is authenticated" do + %{conn: conn} = oauth_access(["read:statuses"]) + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"}) + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"}) + assert length(json_response(res_conn, 200)) == 2 + end + end + + describe "public with restrict unauthenticated timeline for local" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :timelines, :local]) do + Config.put([:restrict_unauthenticated, :timelines, :local], true) + end + + test "if user is unauthenticated", %{conn: conn} do + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"}) + + assert json_response(res_conn, :unauthorized) == %{ + "error" => "authorization required for timeline view" + } + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"}) + assert length(json_response(res_conn, 200)) == 2 + end + + test "if user is authenticated", %{conn: _conn} do + %{conn: conn} = oauth_access(["read:statuses"]) + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"}) + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"}) + assert length(json_response(res_conn, 200)) == 2 + end + end + + describe "public with restrict unauthenticated timeline for remote" do + setup do: local_and_remote_activities() + + clear_config([:restrict_unauthenticated, :timelines, :federated]) do + Config.put([:restrict_unauthenticated, :timelines, :federated], true) + end + + test "if user is unauthenticated", %{conn: conn} do + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"}) + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"}) + + assert json_response(res_conn, :unauthorized) == %{ + "error" => "authorization required for timeline view" + } + end + + test "if user is authenticated", %{conn: _conn} do + %{conn: conn} = oauth_access(["read:statuses"]) + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "true"}) + assert length(json_response(res_conn, 200)) == 1 + + res_conn = get(conn, "/api/v1/timelines/public", %{"local" => "false"}) + assert length(json_response(res_conn, 200)) == 2 + end + end + describe "direct" do test "direct timeline", %{conn: conn} do user_one = insert(:user)