forked from AkkomaGang/akkoma
Merge branch 'refactor/ingestion-activity-context' into 'develop'
ObjectValidators.{Announce,EmojiReact,Like}: Fix context, actor & addressing See merge request pleroma/pleroma!3462
This commit is contained in:
commit
3972d7117e
8 changed files with 105 additions and 105 deletions
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
@ -23,7 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator do
|
||||||
field(:type, :string)
|
field(:type, :string)
|
||||||
field(:object, ObjectValidators.ObjectID)
|
field(:object, ObjectValidators.ObjectID)
|
||||||
field(:actor, ObjectValidators.ObjectID)
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
field(:context, :string, autogenerate: {Utils, :generate_context_id, []})
|
field(:context, :string)
|
||||||
field(:to, ObjectValidators.Recipients, default: [])
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
field(:cc, ObjectValidators.Recipients, default: [])
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
field(:published, ObjectValidators.DateTime)
|
field(:published, ObjectValidators.DateTime)
|
||||||
|
@ -36,6 +37,10 @@ def cast_and_validate(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> fix()
|
||||||
|
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> changeset(data)
|
|> changeset(data)
|
||||||
end
|
end
|
||||||
|
@ -43,11 +48,21 @@ def cast_data(data) do
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
|> fix_after_cast()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_after_cast(cng) do
|
defp fix(data) do
|
||||||
cng
|
data =
|
||||||
|
data
|
||||||
|
|> CommonFixes.fix_actor()
|
||||||
|
|> CommonFixes.fix_activity_addressing()
|
||||||
|
|
||||||
|
with %Object{} = object <- Object.normalize(data["object"]) do
|
||||||
|
data
|
||||||
|
|> CommonFixes.fix_activity_context(object)
|
||||||
|
|> CommonFixes.fix_object_action_recipients(object)
|
||||||
|
else
|
||||||
|
_ -> data
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp validate_data(data_cng) do
|
defp validate_data(data_cng) do
|
||||||
|
@ -60,7 +75,7 @@ defp validate_data(data_cng) do
|
||||||
|> validate_announcable()
|
|> validate_announcable()
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_announcable(cng) do
|
defp validate_announcable(cng) do
|
||||||
with actor when is_binary(actor) <- get_field(cng, :actor),
|
with actor when is_binary(actor) <- get_field(cng, :actor),
|
||||||
object when is_binary(object) <- get_field(cng, :object),
|
object when is_binary(object) <- get_field(cng, :object),
|
||||||
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
%User{} = actor <- User.get_cached_by_ap_id(actor),
|
||||||
|
@ -91,7 +106,7 @@ def validate_announcable(cng) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_existing_announce(cng) do
|
defp validate_existing_announce(cng) do
|
||||||
actor = get_field(cng, :actor)
|
actor = get_field(cng, :actor)
|
||||||
object = get_field(cng, :object)
|
object = get_field(cng, :object)
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
alias Pleroma.Object
|
||||||
alias Pleroma.Object.Containment
|
alias Pleroma.Object.Containment
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.Transmogrifier
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
@ -36,7 +37,7 @@ def fix_object_defaults(data) do
|
||||||
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_activity_addressing(activity, _meta) do
|
def fix_activity_addressing(activity) do
|
||||||
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|
||||||
|
|
||||||
activity
|
activity
|
||||||
|
@ -57,4 +58,21 @@ def fix_actor(data) do
|
||||||
|> Map.put("actor", actor)
|
|> Map.put("actor", actor)
|
||||||
|> Map.put("attributedTo", actor)
|
|> Map.put("attributedTo", actor)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fix_activity_context(data, %Object{data: %{"context" => object_context}}) do
|
||||||
|
data
|
||||||
|
|> Map.put("context", object_context)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_object_action_recipients(%{"actor" => actor} = data, %Object{data: %{"actor" => actor}}) do
|
||||||
|
to = ((data["to"] || []) -- [actor]) |> Enum.uniq()
|
||||||
|
|
||||||
|
Map.put(data, "to", to)
|
||||||
|
end
|
||||||
|
|
||||||
|
def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do
|
||||||
|
to = ((data["to"] || []) ++ [actor]) |> Enum.uniq()
|
||||||
|
|
||||||
|
Map.put(data, "to", to)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator do
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
@ -31,6 +32,10 @@ def cast_and_validate(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> fix()
|
||||||
|
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> changeset(data)
|
|> changeset(data)
|
||||||
end
|
end
|
||||||
|
@ -38,28 +43,24 @@ def cast_data(data) do
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
|> fix_after_cast()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_after_cast(cng) do
|
defp fix(data) do
|
||||||
cng
|
data =
|
||||||
|> fix_context()
|
data
|
||||||
end
|
|> CommonFixes.fix_actor()
|
||||||
|
|> CommonFixes.fix_activity_addressing()
|
||||||
|
|
||||||
def fix_context(cng) do
|
with %Object{} = object <- Object.normalize(data["object"]) do
|
||||||
object = get_field(cng, :object)
|
data
|
||||||
|
|> CommonFixes.fix_activity_context(object)
|
||||||
with nil <- get_field(cng, :context),
|
|> CommonFixes.fix_object_action_recipients(object)
|
||||||
%Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
|
|
||||||
cng
|
|
||||||
|> put_change(:context, context)
|
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> data
|
||||||
cng
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_emoji(cng) do
|
defp validate_emoji(cng) do
|
||||||
content = get_field(cng, :content)
|
content = get_field(cng, :content)
|
||||||
|
|
||||||
if Pleroma.Emoji.is_unicode_emoji?(content) do
|
if Pleroma.Emoji.is_unicode_emoji?(content) do
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
|
|
||||||
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
import Ecto.Changeset
|
import Ecto.Changeset
|
||||||
|
@ -31,6 +32,10 @@ def cast_and_validate(data) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_data(data) do
|
def cast_data(data) do
|
||||||
|
data =
|
||||||
|
data
|
||||||
|
|> fix()
|
||||||
|
|
||||||
%__MODULE__{}
|
%__MODULE__{}
|
||||||
|> changeset(data)
|
|> changeset(data)
|
||||||
end
|
end
|
||||||
|
@ -38,41 +43,20 @@ def cast_data(data) do
|
||||||
def changeset(struct, data) do
|
def changeset(struct, data) do
|
||||||
struct
|
struct
|
||||||
|> cast(data, __schema__(:fields))
|
|> cast(data, __schema__(:fields))
|
||||||
|> fix_after_cast()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def fix_after_cast(cng) do
|
defp fix(data) do
|
||||||
cng
|
data =
|
||||||
|> fix_recipients()
|
data
|
||||||
|> fix_context()
|
|> CommonFixes.fix_actor()
|
||||||
end
|
|> CommonFixes.fix_activity_addressing()
|
||||||
|
|
||||||
def fix_context(cng) do
|
with %Object{} = object <- Object.normalize(data["object"]) do
|
||||||
object = get_field(cng, :object)
|
data
|
||||||
|
|> CommonFixes.fix_activity_context(object)
|
||||||
with nil <- get_field(cng, :context),
|
|> CommonFixes.fix_object_action_recipients(object)
|
||||||
%Object{data: %{"context" => context}} <- Object.get_cached_by_ap_id(object) do
|
|
||||||
cng
|
|
||||||
|> put_change(:context, context)
|
|
||||||
else
|
else
|
||||||
_ ->
|
_ -> data
|
||||||
cng
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def fix_recipients(cng) do
|
|
||||||
to = get_field(cng, :to)
|
|
||||||
cc = get_field(cng, :cc)
|
|
||||||
object = get_field(cng, :object)
|
|
||||||
|
|
||||||
with {[], []} <- {to, cc},
|
|
||||||
%Object{data: %{"actor" => actor}} <- Object.get_cached_by_ap_id(object),
|
|
||||||
{:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
|
|
||||||
cng
|
|
||||||
|> put_change(:to, [actor])
|
|
||||||
else
|
|
||||||
_ ->
|
|
||||||
cng
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -85,7 +69,7 @@ defp validate_data(data_cng) do
|
||||||
|> validate_existing_like()
|
|> validate_existing_like()
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
|
defp validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
|
||||||
if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
|
if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
|
||||||
cng
|
cng
|
||||||
|> add_error(:actor, "already liked this object")
|
|> add_error(:actor, "already liked this object")
|
||||||
|
@ -95,5 +79,5 @@ def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_existing_like(cng), do: cng
|
defp validate_existing_like(cng), do: cng
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,6 +33,18 @@ test "returns ok for a valid announce", %{valid_announce: valid_announce} do
|
||||||
assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
|
assert {:ok, _object, _meta} = ObjectValidator.validate(valid_announce, [])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "keeps announced object context", %{valid_announce: valid_announce} do
|
||||||
|
assert %Object{data: %{"context" => object_context}} =
|
||||||
|
Object.get_cached_by_ap_id(valid_announce["object"])
|
||||||
|
|
||||||
|
{:ok, %{"context" => context}, _} =
|
||||||
|
valid_announce
|
||||||
|
|> Map.put("context", "https://example.org/invalid_context_id")
|
||||||
|
|> ObjectValidator.validate([])
|
||||||
|
|
||||||
|
assert context == object_context
|
||||||
|
end
|
||||||
|
|
||||||
test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
|
test "returns an error if the object can't be found", %{valid_announce: valid_announce} do
|
||||||
without_object =
|
without_object =
|
||||||
valid_announce
|
valid_announce
|
||||||
|
@ -51,16 +63,6 @@ test "returns an error if the object can't be found", %{valid_announce: valid_an
|
||||||
assert {:object, {"can't find object", []}} in cng.errors
|
assert {:object, {"can't find object", []}} in cng.errors
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns an error if we don't have the actor", %{valid_announce: valid_announce} do
|
|
||||||
nonexisting_actor =
|
|
||||||
valid_announce
|
|
||||||
|> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
|
|
||||||
|
|
||||||
{:error, cng} = ObjectValidator.validate(nonexisting_actor, [])
|
|
||||||
|
|
||||||
assert {:actor, {"can't find user", []}} in cng.errors
|
|
||||||
end
|
|
||||||
|
|
||||||
test "returns an error if the actor already announced the object", %{
|
test "returns an error if the actor already announced the object", %{
|
||||||
valid_announce: valid_announce,
|
valid_announce: valid_announce,
|
||||||
announcer: announcer,
|
announcer: announcer,
|
||||||
|
|
|
@ -40,17 +40,30 @@ test "is valid for a valid object", %{valid_like: valid_like} do
|
||||||
assert LikeValidator.cast_and_validate(valid_like).valid?
|
assert LikeValidator.cast_and_validate(valid_like).valid?
|
||||||
end
|
end
|
||||||
|
|
||||||
test "sets the 'to' field to the object actor if no recipients are given", %{
|
test "Add object actor from 'to' field if it doesn't owns the like", %{valid_like: valid_like} do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
object_actor = valid_like["actor"]
|
||||||
|
|
||||||
|
valid_like =
|
||||||
|
valid_like
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|> Map.put("to", [])
|
||||||
|
|
||||||
|
{:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
|
||||||
|
assert object_actor in object["to"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "Removes object actor from 'to' field if it owns the like", %{
|
||||||
valid_like: valid_like,
|
valid_like: valid_like,
|
||||||
user: user
|
user: user
|
||||||
} do
|
} do
|
||||||
without_recipients =
|
valid_like =
|
||||||
valid_like
|
valid_like
|
||||||
|> Map.delete("to")
|
|> Map.put("to", [user.ap_id])
|
||||||
|
|
||||||
{:ok, object, _meta} = ObjectValidator.validate(without_recipients, [])
|
{:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
|
||||||
|
refute user.ap_id in object["to"]
|
||||||
assert object["to"] == [user.ap_id]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
test "sets the context field to the context of the object if no context is given", %{
|
test "sets the context field to the context of the object if no context is given", %{
|
||||||
|
@ -66,16 +79,6 @@ test "sets the context field to the context of the object if no context is given
|
||||||
assert object["context"] == post_activity.data["context"]
|
assert object["context"] == post_activity.data["context"]
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
|
||||||
without_actor = Map.delete(valid_like, "actor")
|
|
||||||
|
|
||||||
refute LikeValidator.cast_and_validate(without_actor).valid?
|
|
||||||
|
|
||||||
with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
|
|
||||||
|
|
||||||
refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it errors when the object is missing or not known", %{valid_like: valid_like} do
|
test "it errors when the object is missing or not known", %{valid_like: valid_like} do
|
||||||
without_object = Map.delete(valid_like, "object")
|
without_object = Map.delete(valid_like, "object")
|
||||||
|
|
||||||
|
|
|
@ -148,7 +148,7 @@ test "returns error when object is unknown" do
|
||||||
assert {:ok, %Activity{} = activity} = Relay.publish(note)
|
assert {:ok, %Activity{} = activity} = Relay.publish(note)
|
||||||
assert activity.data["type"] == "Announce"
|
assert activity.data["type"] == "Announce"
|
||||||
assert activity.data["actor"] == service_actor.ap_id
|
assert activity.data["actor"] == service_actor.ap_id
|
||||||
assert activity.data["to"] == [service_actor.follower_address]
|
assert service_actor.follower_address in activity.data["to"]
|
||||||
assert called(Pleroma.Web.Federator.publish(activity))
|
assert called(Pleroma.Web.Federator.publish(activity))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -150,27 +150,4 @@ test "it rejects incoming announces with an inlined activity from another origin
|
||||||
|
|
||||||
assert {:error, _e} = Transmogrifier.handle_incoming(data)
|
assert {:error, _e} = Transmogrifier.handle_incoming(data)
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it does not clobber the addressing on announce activities" do
|
|
||||||
user = insert(:user)
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{status: "hey"})
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-announce.json")
|
|
||||||
|> Jason.decode!()
|
|
||||||
|> Map.put("object", Object.normalize(activity, fetch: false).data["id"])
|
|
||||||
|> Map.put("to", ["http://mastodon.example.org/users/admin/followers"])
|
|
||||||
|> Map.put("cc", [])
|
|
||||||
|
|
||||||
_user =
|
|
||||||
insert(:user,
|
|
||||||
local: false,
|
|
||||||
ap_id: data["actor"],
|
|
||||||
follower_address: "http://mastodon.example.org/users/admin/followers"
|
|
||||||
)
|
|
||||||
|
|
||||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
assert data["to"] == ["http://mastodon.example.org/users/admin/followers"]
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue