diff options
9 files changed, 125 insertions, 11 deletions
diff --git a/installation/pleroma.nginx b/installation/pleroma.nginx index 78327594f..a333d116c 100644 --- a/installation/pleroma.nginx +++ b/installation/pleroma.nginx @@ -77,6 +77,7 @@ server { add_header X-Content-Type-Options nosniff; add_header Referrer-Policy same-origin; add_header X-Download-Options noopen; + add_header Content-Security-Policy "default-src 'none'; base-uri 'self'; form-action 'self'; img-src 'self' data: https:; media-src 'self' https:; style-src 'self' 'unsafe-inline'; font-src 'self'; script-src 'self'; connect-src 'self' wss://example.tld; upgrade-insecure-requests;"; # Uncomment this only after you get HTTPS working. # add_header Strict-Transport-Security "max-age=31536000; includeSubDomains"; diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 68b398786..e6c2dc9cf 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -14,8 +14,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # For Announce activities, we filter the recipients based on following status for any actors # that match actual users. See issue #164 for more information about why this is necessary. - def get_recipients(%{"type" => "Announce"} = data) do - recipients = (data["to"] || []) ++ (data["cc"] || []) + defp get_recipients(%{"type" => "Announce"} = data) do + to = data["to"] || [] + cc = data["cc"] || [] + recipients = to ++ cc actor = User.get_cached_by_ap_id(data["actor"]) recipients @@ -28,10 +30,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do User.following?(user, actor) end end) + + {recipients, to, cc} end - def get_recipients(data) do - (data["to"] || []) ++ (data["cc"] || []) + defp get_recipients(data) do + to = data["to"] || [] + cc = data["cc"] || [] + recipients = to ++ cc + {recipients, to, cc} end defp check_actor_is_active(actor) do @@ -53,12 +60,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do :ok <- check_actor_is_active(map["actor"]), {:ok, map} <- MRF.filter(map), :ok <- insert_full_object(map) do + {recipients, _, _} = get_recipients(map) + {:ok, activity} = Repo.insert(%Activity{ data: map, local: local, actor: map["actor"], - recipients: get_recipients(map) + recipients: recipients }) Notification.create_notifications(activity) @@ -399,6 +408,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_tag(query, _), do: query + defp restrict_to_cc(query, recipients_to, recipients_cc) do + from( + activity in query, + where: + fragment( + "(?->'to' \\?| ?) or (?->'cc' \\?| ?)", + activity.data, + ^recipients_to, + activity.data, + ^recipients_cc + ) + ) + end + defp restrict_recipients(query, [], _user), do: query defp restrict_recipients(query, recipients, nil) do @@ -540,6 +563,13 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Enum.reverse() end + def fetch_activities_bounded(recipients_to, recipients_cc, opts \\ %{}) do + fetch_activities_query([], opts) + |> restrict_to_cc(recipients_to, recipients_cc) + |> Repo.all() + |> Enum.reverse() + end + def upload(file) do data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media]) Repo.insert(%Object{data: data}) diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index f482de6fd..c90f9fa05 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -850,9 +850,14 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do |> Map.put("type", "Create") |> Map.put("blocking_user", user) - # adding title is a hack to not make empty lists function like a public timeline + # we must filter the following list for the user to avoid leaking statuses the user + # does not actually have permission to see (for more info, peruse security issue #270). + following_to = + following + |> Enum.filter(fn x -> x in user.following end) + activities = - ActivityPub.fetch_activities([title | following], params) + ActivityPub.fetch_activities_bounded(following_to, following, params) |> Enum.reverse() conn diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index d9edcae7f..133cae3b5 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do %{ id: to_string(user.id), - username: hd(String.split(user.nickname, "@")), + username: username_from_nickname(user.nickname), acct: user.nickname, display_name: user.name || user.nickname, locked: user_info.locked, @@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do %{ id: to_string(user.id), acct: user.nickname, - username: hd(String.split(user.nickname, "@")), + username: username_from_nickname(user.nickname), url: user.ap_id } end @@ -76,4 +76,10 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do def render("relationships.json", %{user: user, targets: targets}) do render_many(targets, AccountView, "relationship.json", user: user, as: :target) end + + defp username_from_nickname(string) when is_binary(string) do + hd(String.split(string, "@")) + end + + defp username_from_nickname(_), do: nil end diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index c61bad830..6b6d40346 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -1,7 +1,8 @@ defmodule Pleroma.Web.Streamer do use GenServer require Logger - alias Pleroma.{User, Notification, Activity, Object} + alias Pleroma.{User, Notification, Activity, Object, Repo} + alias Pleroma.Web.ActivityPub.ActivityPub def init(args) do {:ok, args} @@ -60,8 +61,24 @@ defmodule Pleroma.Web.Streamer do end def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do + author = User.get_cached_by_ap_id(item.data["actor"]) + + # filter the recipient list if the activity is not public, see #270. + recipient_lists = + case ActivityPub.is_public?(item) do + true -> + Pleroma.List.get_lists_from_activity(item) + + _ -> + Pleroma.List.get_lists_from_activity(item) + |> Enum.filter(fn list -> + owner = Repo.get(User, list.user_id) + author.follower_address in owner.following + end) + end + recipient_topics = - Pleroma.List.get_lists_from_activity(item) + recipient_lists |> Enum.map(fn %{id: id} -> "list:#{id}" end) Enum.each(recipient_topics || [], fn list_topic -> diff --git a/priv/repo/migrations/20180829082446_add_recipients_to_and_cc_fields_to_activities.exs b/priv/repo/migrations/20180829082446_add_recipients_to_and_cc_fields_to_activities.exs new file mode 100644 index 000000000..96af412f0 --- /dev/null +++ b/priv/repo/migrations/20180829082446_add_recipients_to_and_cc_fields_to_activities.exs @@ -0,0 +1,13 @@ +defmodule Pleroma.Repo.Migrations.AddRecipientsToAndCcFieldsToActivities do + use Ecto.Migration + + def change do + alter table(:activities) do + add :recipients_to, {:array, :string} + add :recipients_cc, {:array, :string} + end + + create index(:activities, [:recipients_to], using: :gin) + create index(:activities, [:recipients_cc], using: :gin) + end +end diff --git a/priv/repo/migrations/20180829182612_activities_add_to_cc_indices.exs b/priv/repo/migrations/20180829182612_activities_add_to_cc_indices.exs new file mode 100644 index 000000000..f6c622e3e --- /dev/null +++ b/priv/repo/migrations/20180829182612_activities_add_to_cc_indices.exs @@ -0,0 +1,8 @@ +defmodule Pleroma.Repo.Migrations.ActivitiesAddToCcIndices do + use Ecto.Migration + + def change do + create index(:activities, ["(data->'to')"], name: :activities_to_index, using: :gin) + create index(:activities, ["(data->'cc')"], name: :activities_cc_index, using: :gin) + end +end diff --git a/priv/repo/migrations/20180829183529_remove_recipients_to_and_cc_fields_from_activities.exs b/priv/repo/migrations/20180829183529_remove_recipients_to_and_cc_fields_from_activities.exs new file mode 100644 index 000000000..ed4f5af30 --- /dev/null +++ b/priv/repo/migrations/20180829183529_remove_recipients_to_and_cc_fields_from_activities.exs @@ -0,0 +1,10 @@ +defmodule Pleroma.Repo.Migrations.RemoveRecipientsToAndCcFieldsFromActivities do + use Ecto.Migration + + def change do + alter table(:activities) do + remove :recipients_to + remove :recipients_cc + end + end +end diff --git a/test/web/mastodon_api/mastodon_api_controller_test.exs b/test/web/mastodon_api/mastodon_api_controller_test.exs index 9e33c1d04..d4ff16c68 100644 --- a/test/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/web/mastodon_api/mastodon_api_controller_test.exs @@ -368,6 +368,30 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do assert id == to_string(activity_two.id) end + + test "list timeline does not leak non-public statuses for unfollowed users", %{conn: conn} do + user = insert(:user) + other_user = insert(:user) + {:ok, activity_one} = TwitterAPI.create_status(other_user, %{"status" => "Marisa is cute."}) + + {:ok, activity_two} = + TwitterAPI.create_status(other_user, %{ + "status" => "Marisa is cute.", + "visibility" => "private" + }) + + {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.follow(list, other_user) + + conn = + conn + |> assign(:user, user) + |> get("/api/v1/timelines/list/#{list.id}") + + assert [%{"id" => id}] = json_response(conn, 200) + + assert id == to_string(activity_one.id) + end end describe "notifications" do |