CommonAPI: Refactor visibility, forbid public to private replies.

This commit is contained in:
lain 2019-05-15 16:30:08 +02:00
parent 153c15cdb2
commit cbb3451023
6 changed files with 76 additions and 30 deletions

View file

@ -58,4 +58,28 @@ def entire_thread_visible_for_user?(
visible_for_user?(tail, user) visible_for_user?(tail, user)
end end
end end
def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
public in to ->
"public"
public in cc ->
"unlisted"
# this should use the sql for the object's activity
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
length(cc) > 0 ->
"private"
true ->
"direct"
end
end
end end

View file

@ -126,22 +126,30 @@ def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(stat
"public" "public"
in_reply_to -> in_reply_to ->
# XXX: these heuristics should be moved out of MastodonAPI. get_replied_to_visibility(in_reply_to)
with %Object{} = object <- Object.normalize(in_reply_to) do
Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
end
end end
end end
def get_visibility(_), do: "public" def get_visibility(_), do: "public"
def get_replied_to_visibility(nil), do: nil
def get_replied_to_visibility(activity) do
with %Object{} = object <- Object.normalize(activity) do
Pleroma.Web.ActivityPub.Visibility.get_visibility(object)
end
end
def post(user, %{"status" => status} = data) do def post(user, %{"status" => status} = data) do
visibility = get_visibility(data)
limit = Pleroma.Config.get([:instance, :limit]) limit = Pleroma.Config.get([:instance, :limit])
with status <- String.trim(status), with status <- String.trim(status),
attachments <- attachments_from_ids(data), attachments <- attachments_from_ids(data),
visibility <- get_visibility(data),
in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]), in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]),
in_reply_to_visibility <- get_replied_to_visibility(in_reply_to),
{_, false} <-
{:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"},
{content_html, mentions, tags} <- {content_html, mentions, tags} <-
make_content_html( make_content_html(
status, status,
@ -185,6 +193,8 @@ def post(user, %{"status" => status} = data) do
) )
res res
else
e -> {:error, e}
end end
end end

View file

@ -16,6 +16,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy alias Pleroma.Web.MediaProxy
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
# TODO: Add cached version. # TODO: Add cached version.
defp get_replied_to_activities(activities) do defp get_replied_to_activities(activities) do
activities activities
@ -340,30 +342,6 @@ def get_reply_to(%{data: %{"object" => _object}} = activity, _) do
end end
end end
def get_visibility(object) do
public = "https://www.w3.org/ns/activitystreams#Public"
to = object.data["to"] || []
cc = object.data["cc"] || []
cond do
public in to ->
"public"
public in cc ->
"unlisted"
# this should use the sql for the object's activity
Enum.any?(to, &String.contains?(&1, "/followers")) ->
"private"
length(cc) > 0 ->
"private"
true ->
"direct"
end
end
def render_content(%{data: %{"type" => "Video"}} = object) do def render_content(%{data: %{"type" => "Video"}} = object) do
with name when not is_nil(name) and name != "" <- object.data["name"] do with name when not is_nil(name) and name != "" <- object.data["name"] do
"<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}" "<p><a href=\"#{object.data["id"]}\">#{name}</a></p>#{object.data["content"]}"

View file

@ -310,7 +310,7 @@ def render(
"tags" => tags, "tags" => tags,
"activity_type" => "post", "activity_type" => "post",
"possibly_sensitive" => possibly_sensitive, "possibly_sensitive" => possibly_sensitive,
"visibility" => StatusView.get_visibility(object), "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
"summary" => summary, "summary" => summary,
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]), "summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
"card" => card, "card" => card,

View file

@ -95,4 +95,16 @@ test "visible_for_user?", %{
refute Visibility.visible_for_user?(private, unrelated) refute Visibility.visible_for_user?(private, unrelated)
refute Visibility.visible_for_user?(direct, unrelated) refute Visibility.visible_for_user?(direct, unrelated)
end end
test "get_visibility", %{
public: public,
private: private,
direct: direct,
unlisted: unlisted
} do
assert Visibility.get_visibility(public) == "public"
assert Visibility.get_visibility(private) == "private"
assert Visibility.get_visibility(direct) == "direct"
assert Visibility.get_visibility(unlisted) == "unlisted"
end
end end

View file

@ -87,6 +87,28 @@ test "it filters out obviously bad tags when accepting a post as Markdown" do
assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')" assert object.data["content"] == "<p><b>2hu</b></p>alert('xss')"
end end
test "it does not allow replies to direct messages that are not direct messages themselves" do
user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "suya..", "visibility" => "direct"})
assert {:ok, _} =
CommonAPI.post(user, %{
"status" => "suya..",
"visibility" => "direct",
"in_reply_to_status_id" => activity.id
})
Enum.each(["public", "private", "unlisted"], fn visibility ->
assert {:error, {:private_to_public, _}} =
CommonAPI.post(user, %{
"status" => "suya..",
"visibility" => visibility,
"in_reply_to_status_id" => activity.id
})
end)
end
end end
describe "reactions" do describe "reactions" do