Couple of fixes for groups

- Fix posts update federation and add tests
- Fix posts deletion federation and add tests

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-10-19 09:32:37 +02:00
parent 0c4a7e0216
commit fc1d392211
12 changed files with 537 additions and 239 deletions

View File

@@ -15,6 +15,7 @@ defmodule Mobilizon.Federation.ActivityPub do
Config,
Discussions,
Events,
Posts,
Resources,
Share,
Users
@@ -88,6 +89,7 @@ defmodule Mobilizon.Federation.ActivityPub do
{:existing, Discussions.get_discussion_by_url(url)},
{:existing, nil} <- {:existing, Discussions.get_comment_from_url(url)},
{:existing, nil} <- {:existing, Resources.get_resource_by_url(url)},
{:existing, nil} <- {:existing, Posts.get_post_by_url(url)},
{:existing, nil} <-
{:existing, Actors.get_actor_by_url_2(url)},
{:existing, nil} <- {:existing, Actors.get_member_by_url(url)},
@@ -109,6 +111,9 @@ defmodule Mobilizon.Federation.ActivityPub do
{:error, "Gone"} ->
{:error, "Gone", entity}
{:error, "Not found"} ->
{:error, "Not found", entity}
end
else
{:ok, entity}

View File

@@ -50,7 +50,7 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do
def handle(:incoming_ap_doc, params) do
Logger.info("Handling incoming AP activity")
Logger.debug(inspect(params))
Logger.debug(inspect(Map.drop(params, ["@context"])))
case Transmogrifier.handle_incoming(params) do
{:ok, activity, _data} ->

View File

@@ -32,6 +32,10 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
Logger.warn("Resource at #{url} is 410 Gone")
{:error, "Gone"}
{:ok, %Tesla.Env{status: 404}} ->
Logger.warn("Resource at #{url} is 404 Gone")
{:error, "Not found"}
{:ok, %Tesla.Env{} = res} ->
{:error, res}
end

View File

@@ -4,10 +4,11 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
"""
# TODO: Move me in a more appropriate place
alias Mobilizon.{Actors, Discussions, Events, Resources}
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources}
alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Discussions.{Comment, Discussion}
alias Mobilizon.Events.Event
alias Mobilizon.Posts.Post
alias Mobilizon.Resources.Resource
alias Mobilizon.Tombstone
@@ -23,6 +24,9 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
def maybe_preload(%Resource{url: url}),
do: {:ok, Resources.get_resource_by_url_with_preloads(url)}
def maybe_preload(%Post{url: url}),
do: {:ok, Posts.get_post_by_url_with_preloads(url)}
def maybe_preload(%Actor{url: url}), do: {:ok, Actors.get_actor_by_url!(url, true)}
def maybe_preload(%Member{} = member), do: {:ok, member}

View File

@@ -118,7 +118,9 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
defp process_collection(_, _), do: :error
defp handling_element(data) when is_map(data) do
# If we're handling an activity
defp handling_element(%{"type" => activity_type} = data)
when activity_type in ["Create", "Update", "Delete"] do
object = get_in(data, ["object"])
if object do
@@ -128,6 +130,26 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
Transmogrifier.handle_incoming(data)
end
# If we're handling directly an object
defp handling_element(data) when is_map(data) do
object = get_in(data, ["object"])
if object do
object |> Utils.get_url() |> Mobilizon.Tombstone.delete_uri_tombstone()
end
activity = %{
"type" => "Create",
"to" => data["to"],
"cc" => data["cc"],
"actor" => data["actor"] || data["attributedTo"],
"attributedTo" => data["attributedTo"] || data["actor"],
"object" => data
}
Transmogrifier.handle_incoming(activity)
end
defp handling_element(uri) when is_binary(uri) do
ActivityPub.fetch_object_from_url(uri)
end

View File

@@ -418,6 +418,50 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end
end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => "Article"} = object, "actor" => _actor} =
update_data
) do
with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor),
{:ok, %Post{} = old_post} <-
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
object_data <- Converter.Post.as_to_model_data(object),
{:origin_check, true} <-
{:origin_check,
Utils.origin_check?(actor_url, update_data["object"]) ||
Utils.activity_actor_is_group_member?(actor, old_post)},
{:ok, %Activity{} = activity, %Post{} = new_post} <-
ActivityPub.update(old_post, object_data, false) do
{:ok, activity, new_post}
else
_e ->
:error
end
end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => type} = object, "actor" => _actor} =
update_data
)
when type in ["ResourceCollection", "Document"] do
with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false}} <-
ActivityPub.get_or_fetch_actor_by_url(actor),
{:ok, %Resource{} = old_resource} <-
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
object_data <- Converter.Resource.as_to_model_data(object),
{:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)},
{:ok, %Activity{} = activity, %Resource{} = new_resource} <-
ActivityPub.update(old_resource, object_data, false) do
{:ok, activity, new_resource}
else
_e ->
:error
end
end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => "Member"} = object, "actor" => _actor} =
update_data
@@ -505,7 +549,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
with actor_url <- Utils.get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url),
object_id <- Utils.get_url(object),
{:error, "Gone", object} <- ActivityPub.fetch_object_from_url(object_id, force: true),
{:ok, object} <- can_delete_group_object(object_id),
{:origin_check, true} <-
{:origin_check,
Utils.origin_check_from_id?(actor_url, object_id) ||
@@ -975,4 +1019,25 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
fetch_object_optionnally_authenticated(url, actor)
end
end
defp can_delete_group_object(object_id) do
case ActivityPub.fetch_object_from_url(object_id, force: true) do
{:error, error_message, object} when error_message in ["Gone", "Not found"] ->
{:ok, object}
{:ok, %{url: url} = object} ->
if Utils.are_same_origin?(url, Endpoint.url()),
do: {:ok, object},
else: {:error, "Group object URL remote"}
{:error, {:error, err}} ->
{:error, err}
{:error, err} ->
{:error, err}
err ->
err
end
end
end

View File

@@ -1,6 +1,6 @@
defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
@moduledoc false
alias Mobilizon.{Actors, Posts}
alias Mobilizon.{Actors, Posts, Tombstone}
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Types.Entity
alias Mobilizon.Federation.ActivityStream.Converter.Utils, as: ConverterUtils
@@ -11,6 +11,8 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
@behaviour Entity
@public_ap "https://www.w3.org/ns/activitystreams#Public"
@impl Entity
def create(args, additional) do
with args <- Map.update(args, :tags, [], &ConverterUtils.fetch_tags/1),
@@ -66,7 +68,7 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
def delete(
%Post{
url: url,
attributed_to: %Actor{url: group_url}
attributed_to: %Actor{url: group_url, members_url: members_url}
} = post,
%Actor{url: actor_url} = actor,
_local,
@@ -77,11 +79,13 @@ defmodule Mobilizon.Federation.ActivityPub.Types.Posts do
"type" => "Delete",
"object" => Convertible.model_to_as(post),
"id" => url <> "/delete",
"to" => [group_url]
"to" => [group_url, @public_ap, members_url]
}
with {:ok, _post} <- Posts.delete_post(post),
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}") do
with {:ok, %Post{} = post} <- Posts.delete_post(post),
{:ok, true} <- Cachex.del(:activity_pub, "post_#{post.slug}"),
{:ok, %Tombstone{} = _tombstone} <-
Tombstone.create_tombstone(%{uri: post.url, actor_id: actor.id}) do
{:ok, activity_data, actor, post}
end
end

View File

@@ -287,9 +287,14 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
def origin_check_from_id?(id, %{"id" => other_id} = _params) when is_binary(other_id),
do: origin_check_from_id?(id, other_id)
def activity_actor_is_group_member?(%Actor{id: actor_id}, object) do
def activity_actor_is_group_member?(%Actor{id: actor_id, url: actor_url}, object) do
Logger.debug(
"Checking if activity actor #{actor_url} is a member from group from #{object.url}"
)
case Ownable.group_actor(object) do
%Actor{type: :Group, id: group_id} ->
Logger.debug("Group object ID is #{group_id}")
Actors.is_member?(actor_id, group_id)
_ ->