[WIP] Test transmogrifier

Introduce MobilizonWeb.API namespace

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Format

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

WIP

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

remove unneeded code

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Fix tests

Signed-off-by: Thomas Citharel <tcit@tcit.fr>

Fix warnings

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2018-12-14 17:41:55 +01:00
parent e3a8343112
commit c1e6612405
41 changed files with 2961 additions and 246 deletions

View File

@@ -2,14 +2,36 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
@moduledoc """
A module to handle coding from internal to wire ActivityPub and back.
"""
alias Mobilizon.Actors.Actor
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Events
alias Mobilizon.Events.{Event, Comment}
alias Mobilizon.Service.ActivityPub
alias Mobilizon.Service.ActivityPub.Utils
require Logger
def get_actor(%{"actor" => actor}) when is_binary(actor) do
actor
end
def get_actor(%{"actor" => actor}) when is_list(actor) do
if is_binary(Enum.at(actor, 0)) do
Enum.at(actor, 0)
else
Enum.find(actor, fn %{"type" => type} -> type in ["Person", "Service", "Application"] end)
|> Map.get("id")
end
end
def get_actor(%{"actor" => %{"id" => id}}) when is_bitstring(id) do
id
end
def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor) do
get_actor(%{"actor" => actor})
end
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
@@ -48,6 +70,10 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
object
|> Map.put("inReplyTo", replied_object.url)
{:error, {:error, :not_supported}} ->
Logger.info("Object reply origin has not a supported type")
object
e ->
Logger.error("Couldn't fetch #{in_reply_to_id} #{inspect(e)}")
object
@@ -88,6 +114,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(data["actor"]) do
Logger.debug("found actor")
Logger.debug(inspect(actor))
params = %{
to: data["to"],
@@ -136,78 +163,134 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
# _e -> :error
# end
# end
#
# #
# def handle_incoming(
# %{"type" => "Announce", "object" => object_id, "actor" => actor, "id" => id} = data
# ) do
# with {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
# {:ok, object} <-
# fetch_obj_helper(object_id) || ActivityPub.fetch_object_from_url(object_id),
# {:ok, activity, object} <- ActivityPub.announce(actor, object, id, false) do
# with actor <- get_actor(data),
# {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
# {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
# {:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false) do
# {:ok, activity}
# else
# e -> Logger.error(inspect e)
# :error
# end
# end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => _actor_id} =
data
)
when object_type in ["Person", "Application", "Service", "Organization"] do
with {:ok, %Actor{url: url}} <- Actors.get_actor_by_url(object["id"]) do
{:ok, new_actor_data} = ActivityPub.actor_data_from_actor_object(object)
Actors.insert_or_update_actor(new_actor_data)
ActivityPub.update(%{
local: false,
to: data["to"] || [],
cc: data["cc"] || [],
object: object,
actor: url
})
else
e ->
Logger.error(inspect(e))
:error
end
end
# def handle_incoming(
# %{
# "type" => "Undo",
# "object" => %{"type" => "Announce", "object" => object_id},
# "actor" => actor,
# "id" => id
# } = data
# ) do
# with actor <- get_actor(data),
# {:ok, %Actor{} = actor} <- Actors.get_or_fetch_by_url(actor),
# {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
# {:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
# {:ok, activity}
# else
# _e -> :error
# end
# end
#
# def handle_incoming(
# %{"type" => "Update", "object" => %{"type" => "Person"} = object, "actor" => actor_id} =
# data
# ) do
# with %User{ap_id: ^actor_id} = actor <- User.get_by_ap_id(object["id"]) do
# {:ok, new_user_data} = ActivityPub.actor_data_from_actor_object(object)
#
# banner = new_user_data[:info]["banner"]
#
# update_data =
# new_user_data
# |> Map.take([:name, :bio, :avatar])
# |> Map.put(:info, Map.merge(actor.info, %{"banner" => banner}))
#
# actor
# |> User.upgrade_changeset(update_data)
# |> User.update_and_set_cache()
#
# ActivityPub.update(%{
# local: false,
# to: data["to"] || [],
# cc: data["cc"] || [],
# object: object,
# actor: actor_id
# })
# else
# e ->
# Logger.error(e)
# :error
# end
# end
#
# # TODO: Make secure.
# def handle_incoming(
# %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data
# ) do
# object_id =
# case object_id do
# %{"id" => id} -> id
# id -> id
# end
#
# with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
# {:ok, object} <-
# fetch_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
# {:ok, activity} <- ActivityPub.delete(object, false) do
# {:ok, activity}
# else
# e -> :error
# end
# end
def handle_incoming(
%{
"type" => "Undo",
"object" => %{"type" => "Follow", "object" => followed},
"actor" => follower,
"id" => id
} = _data
) do
with {:ok, %Actor{domain: nil} = followed} <- Actors.get_actor_by_url(followed),
{:ok, %Actor{} = follower} <- Actors.get_actor_by_url(follower),
{:ok, activity} <- ActivityPub.unfollow(followed, follower, id, false) do
Actor.unfollow(follower, followed)
{:ok, activity}
else
e ->
Logger.error(inspect(e))
:error
end
end
# TODO: We presently assume that any actor on the same origin domain as the object being
# deleted has the rights to delete that object. A better way to validate whether or not
# the object should be deleted is to refetch the object URI, which should return either
# an error or a tombstone. This would allow us to verify that a deletion actually took
# place.
def handle_incoming(
%{"type" => "Delete", "object" => object, "actor" => _actor, "id" => _id} = data
) do
object_id = Utils.get_url(object)
with actor <- get_actor(data),
{:ok, %Actor{url: _actor_url}} <- Actors.get_actor_by_url(actor),
{:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
# TODO : Validate that DELETE comes indeed form right domain (see above)
# :ok <- contain_origin(actor_url, object.data),
{:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity}
else
e ->
Logger.debug(inspect(e))
:error
end
end
#
# # TODO
# # Accept
# # Undo
#
def handle_incoming(_), do: :error
# def handle_incoming(
# %{
# "type" => "Undo",
# "object" => %{"type" => "Like", "object" => object_id},
# "actor" => _actor,
# "id" => id
# } = data
# ) do
# with actor <- get_actor(data),
# %Actor{} = actor <- Actors.get_or_fetch_by_url(actor),
# {:ok, object} <- get_obj_helper(object_id) || fetch_obj_helper(object_id),
# {:ok, activity, _, _} <- ActivityPub.unlike(actor, object, id, false) do
# {:ok, activity}
# else
# _e -> :error
# end
# end
def handle_incoming(_) do
Logger.info("Handing something not supported")
{:error, :not_supported}
end
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) do
with false <- String.starts_with?(in_reply_to, "http"),
@@ -224,7 +307,7 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
def prepare_object(object) do
object
# |> set_sensitive
# |> add_hashtags
|> add_hashtags
|> add_mention_tags
# |> add_emoji_tags
|> add_attributed_to
@@ -326,7 +409,13 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
mentions =
recipients
|> Enum.map(fn url -> Actors.get_actor_by_url!(url) end)
|> Enum.filter(& &1)
|> Enum.map(fn url ->
case Actors.get_actor_by_url(url) do
{:ok, actor} -> actor
_ -> nil
end
end)
|> Enum.filter(& &1)
|> Enum.map(fn actor ->
%{"type" => "Mention", "href" => actor.url, "name" => "@#{actor.preferred_username}"}
@@ -391,4 +480,43 @@ defmodule Mobilizon.Service.ActivityPub.Transmogrifier do
@spec fetch_obj_helper(map()) :: {:ok, %Event{}} | {:ok, %Comment{}} | {:error, any()}
def fetch_obj_helper(obj) when is_map(obj), do: ActivityPub.fetch_object_from_url(obj["id"])
@spec get_obj_helper(String.t()) :: {:ok, struct()} | nil
def get_obj_helper(id) do
if object = normalize(id), do: {:ok, object}, else: nil
end
@spec normalize(map()) :: struct() | nil
def normalize(obj) when is_map(obj), do: get_anything_by_url(obj["id"])
@spec normalize(String.t()) :: struct() | nil
def normalize(url) when is_binary(url), do: get_anything_by_url(url)
@spec normalize(any()) :: nil
def normalize(_), do: nil
@spec normalize(String.t()) :: struct() | nil
def get_anything_by_url(url) do
Logger.debug("Getting anything from url #{url}")
get_actor_url(url) || get_event_url(url) || get_comment_url(url)
end
defp get_actor_url(url) do
case Actors.get_actor_by_url(url) do
{:ok, %Actor{} = actor} -> actor
_ -> nil
end
end
defp get_event_url(url) do
case Events.get_event_by_url(url) do
{:ok, %Event{} = event} -> event
_ -> nil
end
end
defp get_comment_url(url) do
case Events.get_comment_full_from_url(url) do
{:ok, %Comment{} = comment} -> comment
_ -> nil
end
end
end