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 | 
