Refactor the ActivityPub module

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-04-22 12:17:56 +02:00
parent 17a6a6eada
commit 280f461ba7
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
32 changed files with 385 additions and 332 deletions

View file

@ -39,11 +39,12 @@ defmodule Mobilizon.Federation.ActivityPub do
Visibility Visibility
} }
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Types.{Managable, Ownable} alias Mobilizon.Federation.ActivityPub.Types.{Managable, Ownable}
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Federation.WebFinger
alias Mobilizon.Service.Notifications.Scheduler alias Mobilizon.Service.Notifications.Scheduler
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
@ -154,40 +155,6 @@ defmodule Mobilizon.Federation.ActivityPub do
end end
end end
@doc """
Getting an actor from url, eventually creating it if we don't have it locally or if it needs an update
"""
@spec get_or_fetch_actor_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
def get_or_fetch_actor_by_url(url, preload \\ false)
def get_or_fetch_actor_by_url(nil, _preload), do: {:error, "Can't fetch a nil url"}
def get_or_fetch_actor_by_url("https://www.w3.org/ns/activitystreams#Public", _preload) do
with %Actor{url: url} <- Relay.get_actor() do
get_or_fetch_actor_by_url(url)
end
end
@spec get_or_fetch_actor_by_url(String.t(), boolean()) :: {:ok, Actor.t()} | {:error, any()}
def get_or_fetch_actor_by_url(url, preload) do
with {:ok, %Actor{} = cached_actor} <- Actors.get_actor_by_url(url, preload),
false <- Actors.needs_update?(cached_actor) do
{:ok, cached_actor}
else
_ ->
# For tests, see https://github.com/jjh42/mock#not-supported---mocking-internal-function-calls and Mobilizon.Federation.ActivityPubTest
case __MODULE__.make_actor_from_url(url, preload) do
{:ok, %Actor{} = actor} ->
{:ok, actor}
{:error, err} ->
Logger.debug("Could not fetch by AP id")
Logger.debug(inspect(err))
{:error, "Could not fetch by AP id"}
end
end
end
@doc """ @doc """
Create an activity of type `Create` Create an activity of type `Create`
@ -302,7 +269,8 @@ defmodule Mobilizon.Federation.ActivityPub do
local \\ true, local \\ true,
public \\ true public \\ true
) do ) do
with {:ok, %Actor{id: object_owner_actor_id}} <- get_or_fetch_actor_by_url(object["actor"]), with {:ok, %Actor{id: object_owner_actor_id}} <-
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
{:ok, %Share{} = _share} <- Share.create(object["id"], actor.id, object_owner_actor_id), {:ok, %Share{} = _share} <- Share.create(object["id"], actor.id, object_owner_actor_id),
announce_data <- make_announce_data(actor, object, activity_id, public), announce_data <- make_announce_data(actor, object, activity_id, public),
{:ok, activity} <- create_activity(announce_data, local), {:ok, activity} <- create_activity(announce_data, local),
@ -619,64 +587,6 @@ defmodule Mobilizon.Federation.ActivityPub do
end end
end end
@doc """
Create an actor locally by its URL (AP ID)
"""
@spec make_actor_from_url(String.t(), boolean()) :: {:ok, %Actor{}} | {:error, any()}
def make_actor_from_url(url, preload \\ false) do
if are_same_origin?(url, Endpoint.url()) do
{:error, "Can't make a local actor from URL"}
else
case fetch_and_prepare_actor_from_url(url) do
{:ok, data} ->
Actors.upsert_actor(data, preload)
# Request returned 410
{:error, :actor_deleted} ->
Logger.info("Actor was deleted")
{:error, :actor_deleted}
{:error, e} ->
Logger.warn("Failed to make actor from url")
{:error, e}
end
end
end
@doc """
Find an actor in our local database or call WebFinger to find what's its AP ID is and then fetch it
"""
@spec find_or_make_actor_from_nickname(String.t(), atom() | nil) :: tuple()
def find_or_make_actor_from_nickname(nickname, type \\ nil) do
case Actors.get_actor_by_name(nickname, type) do
%Actor{} = actor ->
{:ok, actor}
nil ->
make_actor_from_nickname(nickname)
end
end
@spec find_or_make_person_from_nickname(String.t()) :: tuple()
def find_or_make_person_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Person)
@spec find_or_make_group_from_nickname(String.t()) :: tuple()
def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group)
@doc """
Create an actor inside our database from username, using WebFinger to find out its AP ID and then fetch it
"""
@spec make_actor_from_nickname(String.t()) :: {:ok, %Actor{}} | {:error, any()}
def make_actor_from_nickname(nickname) do
case WebFinger.finger(nickname) do
{:ok, url} when is_binary(url) ->
make_actor_from_url(url)
_e ->
{:error, "No ActivityPub URL found in WebFinger"}
end
end
@spec is_create_activity?(Activity.t()) :: boolean @spec is_create_activity?(Activity.t()) :: boolean
defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true defp is_create_activity?(%Activity{data: %{"type" => "Create"}}), do: true
defp is_create_activity?(_), do: false defp is_create_activity?(_), do: false
@ -794,40 +704,6 @@ defmodule Mobilizon.Federation.ActivityPub do
) )
end end
# Fetching a remote actor's information through its AP ID
@spec fetch_and_prepare_actor_from_url(String.t()) :: {:ok, map()} | {:error, atom()} | any()
defp fetch_and_prepare_actor_from_url(url) do
Logger.debug("Fetching and preparing actor from url")
Logger.debug(inspect(url))
res =
with {:ok, %{status: 200, body: body}} <-
Tesla.get(url,
headers: [{"Accept", "application/activity+json"}],
follow_redirect: true
),
:ok <- Logger.debug("response okay, now decoding json"),
{:ok, data} <- Jason.decode(body) do
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
{:ok, Converter.Actor.as_to_model_data(data)}
else
# Actor is gone, probably deleted
{:ok, %{status: 410}} ->
Logger.info("Response HTTP 410")
{:error, :actor_deleted}
{:error, e} ->
Logger.warn("Could not decode actor at fetch #{url}, #{inspect(e)}")
{:error, e}
e ->
Logger.warn("Could not decode actor at fetch #{url}, #{inspect(e)}")
{:error, e}
end
res
end
@doc """ @doc """
Return all public activities (events & comments) for an actor Return all public activities (events & comments) for an actor
""" """

View file

@ -0,0 +1,102 @@
defmodule Mobilizon.Federation.ActivityPub.Actor do
@moduledoc """
Module to handle ActivityPub Actor interactions
"""
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay}
alias Mobilizon.Federation.WebFinger
alias Mobilizon.Web.Endpoint
require Logger
import Mobilizon.Federation.ActivityPub.Utils, only: [are_same_origin?: 2]
@doc """
Getting an actor from url, eventually creating it if we don't have it locally or if it needs an update
"""
@spec get_or_fetch_actor_by_url(String.t(), boolean) :: {:ok, Actor.t()} | {:error, String.t()}
def get_or_fetch_actor_by_url(url, preload \\ false)
def get_or_fetch_actor_by_url(nil, _preload), do: {:error, "Can't fetch a nil url"}
def get_or_fetch_actor_by_url("https://www.w3.org/ns/activitystreams#Public", _preload) do
with %Actor{url: url} <- Relay.get_actor() do
get_or_fetch_actor_by_url(url)
end
end
@spec get_or_fetch_actor_by_url(String.t(), boolean()) :: {:ok, Actor.t()} | {:error, any()}
def get_or_fetch_actor_by_url(url, preload) do
with {:ok, %Actor{} = cached_actor} <- Actors.get_actor_by_url(url, preload),
false <- Actors.needs_update?(cached_actor) do
{:ok, cached_actor}
else
_ ->
# For tests, see https://github.com/jjh42/mock#not-supported---mocking-internal-function-calls and Mobilizon.Federation.ActivityPubTest
case __MODULE__.make_actor_from_url(url, preload) do
{:ok, %Actor{} = actor} ->
{:ok, actor}
{:error, err} ->
Logger.debug("Could not fetch by AP id")
Logger.debug(inspect(err))
{:error, "Could not fetch by AP id"}
end
end
end
@doc """
Create an actor locally by its URL (AP ID)
"""
@spec make_actor_from_url(String.t(), boolean()) :: {:ok, %Actor{}} | {:error, any()}
def make_actor_from_url(url, preload \\ false) do
if are_same_origin?(url, Endpoint.url()) do
{:error, "Can't make a local actor from URL"}
else
case Fetcher.fetch_and_prepare_actor_from_url(url) do
{:ok, data} ->
Actors.upsert_actor(data, preload)
# Request returned 410
{:error, :actor_deleted} ->
Logger.info("Actor was deleted")
{:error, :actor_deleted}
{:error, e} ->
Logger.warn("Failed to make actor from url")
{:error, e}
end
end
end
@doc """
Find an actor in our local database or call WebFinger to find what's its AP ID is and then fetch it
"""
@spec find_or_make_actor_from_nickname(String.t(), atom() | nil) :: tuple()
def find_or_make_actor_from_nickname(nickname, type \\ nil) do
case Actors.get_actor_by_name(nickname, type) do
%Actor{} = actor ->
{:ok, actor}
nil ->
make_actor_from_nickname(nickname)
end
end
@spec find_or_make_group_from_nickname(String.t()) :: tuple()
def find_or_make_group_from_nickname(nick), do: find_or_make_actor_from_nickname(nick, :Group)
@doc """
Create an actor inside our database from username, using WebFinger to find out its AP ID and then fetch it
"""
@spec make_actor_from_nickname(String.t()) :: {:ok, %Actor{}} | {:error, any()}
def make_actor_from_nickname(nickname) do
case WebFinger.finger(nickname) do
{:ok, url} when is_binary(url) ->
make_actor_from_url(url)
_e ->
{:error, "No ActivityPub URL found in WebFinger"}
end
end
end

View file

@ -13,6 +13,7 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
require Logger require Logger
@ -42,7 +43,8 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do
Logger.debug(inspect(activity)) Logger.debug(inspect(activity))
Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end)
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(activity.data["actor"]) do with {:ok, %Actor{} = actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(activity.data["actor"]) do
Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end) Logger.info(fn -> "Sending #{activity.data["id"]} out via AP" end)
ActivityPub.publish(actor, activity) ActivityPub.publish(actor, activity)
end end

View file

@ -8,6 +8,7 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Federation.ActivityPub.{Relay, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Relay, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Converter.Actor, as: ActorConverter
alias Mobilizon.Service.HTTP.ActivityPub, as: ActivityPubClient alias Mobilizon.Service.HTTP.ActivityPub, as: ActivityPubClient
import Mobilizon.Federation.ActivityPub.Utils, import Mobilizon.Federation.ActivityPub.Utils,
@ -94,6 +95,42 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
end end
end end
@doc """
Fetching a remote actor's information through its AP ID
"""
@spec fetch_and_prepare_actor_from_url(String.t()) :: {:ok, map()} | {:error, atom()} | any()
def fetch_and_prepare_actor_from_url(url) do
Logger.debug("Fetching and preparing actor from url")
Logger.debug(inspect(url))
res =
with {:ok, %{status: 200, body: body}} <-
Tesla.get(url,
headers: [{"Accept", "application/activity+json"}],
follow_redirect: true
),
:ok <- Logger.debug("response okay, now decoding json"),
{:ok, data} <- Jason.decode(body) do
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
{:ok, ActorConverter.as_to_model_data(data)}
else
# Actor is gone, probably deleted
{:ok, %{status: 410}} ->
Logger.info("Response HTTP 410")
{:error, :actor_deleted}
{:error, e} ->
Logger.warn("Could not decode actor at fetch #{url}, #{inspect(e)}")
{:error, e}
e ->
Logger.warn("Could not decode actor at fetch #{url}, #{inspect(e)}")
{:error, e}
end
res
end
@spec origin_check(String.t(), map()) :: boolean() @spec origin_check(String.t(), map()) :: boolean()
defp origin_check(url, data) do defp origin_check(url, data) do
if origin_check?(url, data) do if origin_check?(url, data) do

View file

@ -6,6 +6,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils} alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils}
require Logger require Logger
@ -31,7 +32,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
end end
def refresh_profile(%Actor{type: type, url: url}) when type in [:Person, :Application] do def refresh_profile(%Actor{type: type, url: url}) when type in [:Person, :Application] do
with {:ok, %Actor{outbox_url: outbox_url}} <- ActivityPub.make_actor_from_url(url), with {:ok, %Actor{outbox_url: outbox_url}} <- ActivityPubActor.make_actor_from_url(url),
:ok <- fetch_collection(outbox_url, Relay.get_actor()) do :ok <- fetch_collection(outbox_url, Relay.get_actor()) do
:ok :ok
end end
@ -49,7 +50,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
discussions_url: discussions_url, discussions_url: discussions_url,
events_url: events_url events_url: events_url
}} <- }} <-
ActivityPub.make_actor_from_url(group_url), ActivityPubActor.make_actor_from_url(group_url),
:ok <- fetch_collection(outbox_url, on_behalf_of), :ok <- fetch_collection(outbox_url, on_behalf_of),
:ok <- fetch_collection(members_url, on_behalf_of), :ok <- fetch_collection(members_url, on_behalf_of),
:ok <- fetch_collection(resources_url, on_behalf_of), :ok <- fetch_collection(resources_url, on_behalf_of),

View file

@ -13,6 +13,7 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Activity, Refresher, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Activity, Refresher, Transmogrifier}
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.WebFinger alias Mobilizon.Federation.WebFinger
alias Mobilizon.GraphQL.API.Follows alias Mobilizon.GraphQL.API.Follows
@ -37,7 +38,8 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
def follow(address) do def follow(address) do
with {:ok, target_instance} <- fetch_actor(address), with {:ok, target_instance} <- fetch_actor(address),
%Actor{} = local_actor <- get_actor(), %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), {:ok, %Actor{} = target_actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
{:ok, activity, follow} <- Follows.follow(local_actor, target_actor) do {:ok, activity, follow} <- Follows.follow(local_actor, target_actor) do
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}") Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
{:ok, activity, follow} {:ok, activity, follow}
@ -56,7 +58,8 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
def unfollow(address) do def unfollow(address) do
with {:ok, target_instance} <- fetch_actor(address), with {:ok, target_instance} <- fetch_actor(address),
%Actor{} = local_actor <- get_actor(), %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), {:ok, %Actor{} = target_actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
{:ok, activity, follow} <- Follows.unfollow(local_actor, target_actor) do {:ok, activity, follow} <- Follows.unfollow(local_actor, target_actor) do
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}") Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
{:ok, activity, follow} {:ok, activity, follow}
@ -73,7 +76,8 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
with {:ok, target_instance} <- fetch_actor(address), with {:ok, target_instance} <- fetch_actor(address),
%Actor{} = local_actor <- get_actor(), %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), {:ok, %Actor{} = target_actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
{:ok, activity, follow} <- Follows.accept(target_actor, local_actor) do {:ok, activity, follow} <- Follows.accept(target_actor, local_actor) do
{:ok, activity, follow} {:ok, activity, follow}
end end
@ -84,7 +88,8 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
with {:ok, target_instance} <- fetch_actor(address), with {:ok, target_instance} <- fetch_actor(address),
%Actor{} = local_actor <- get_actor(), %Actor{} = local_actor <- get_actor(),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance), {:ok, %Actor{} = target_actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
{:ok, activity, follow} <- Follows.reject(target_actor, local_actor) do {:ok, activity, follow} <- Follows.reject(target_actor, local_actor) do
{:ok, activity, follow} {:ok, activity, follow}
end end
@ -94,7 +99,8 @@ defmodule Mobilizon.Federation.ActivityPub.Relay do
Logger.debug("We're trying to refresh a remote instance") Logger.debug("We're trying to refresh a remote instance")
with {:ok, target_instance} <- fetch_actor(address), with {:ok, target_instance} <- fetch_actor(address),
{:ok, %Actor{} = target_actor} <- ActivityPub.get_or_fetch_actor_by_url(target_instance) do {:ok, %Actor{} = target_actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(target_instance) do
Refresher.refresh_profile(target_actor) Refresher.refresh_profile(target_actor)
end end
end end

View file

@ -18,6 +18,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Activity, Refresher, Relay, Utils} alias Mobilizon.Federation.ActivityPub.{Activity, Refresher, Relay, Utils}
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Types.Ownable alias Mobilizon.Federation.ActivityPub.Types.Ownable
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Tombstone alias Mobilizon.Tombstone
@ -117,12 +118,13 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
def handle_incoming(%{ def handle_incoming(%{
"type" => "Create", "type" => "Create",
"object" => %{"type" => "Group", "id" => group_url} = _object "object" => %{"type" => type, "id" => actor_url} = _object
}) do })
Logger.info("Handle incoming to create a group") when type in ["Group", "Person", "Actor"] do
Logger.info("Handle incoming to create an actor")
with {:ok, %Actor{} = group} <- ActivityPub.get_or_fetch_actor_by_url(group_url) do with {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
{:ok, nil, group} {:ok, nil, actor}
end end
end end
@ -201,8 +203,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
def handle_incoming( def handle_incoming(
%{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = _data
) do ) do
with {:ok, %Actor{} = followed} <- ActivityPub.get_or_fetch_actor_by_url(followed, true), with {:ok, %Actor{} = followed} <- ActivityPubActor.get_or_fetch_actor_by_url(followed, true),
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_actor_by_url(follower), {:ok, %Actor{} = follower} <- ActivityPubActor.get_or_fetch_actor_by_url(follower),
{:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do {:ok, activity, object} <- ActivityPub.follow(follower, followed, id, false) do
{:ok, activity, object} {:ok, activity, object}
else else
@ -221,7 +223,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
with {:existing_todo_list, nil} <- with {:existing_todo_list, nil} <-
{:existing_todo_list, Todos.get_todo_list_by_url(object_url)}, {:existing_todo_list, Todos.get_todo_list_by_url(object_url)},
{:ok, %Actor{url: actor_url}} <- ActivityPub.get_or_fetch_actor_by_url(actor_url), {:ok, %Actor{url: actor_url}} <- ActivityPubActor.get_or_fetch_actor_by_url(actor_url),
object_data when is_map(object_data) <- object_data when is_map(object_data) <-
object |> Converter.TodoList.as_to_model_data(), object |> Converter.TodoList.as_to_model_data(),
{:ok, %Activity{} = activity, %TodoList{} = todo_list} <- {:ok, %Activity{} = activity, %TodoList{} = todo_list} <-
@ -295,7 +297,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
} = data } = data
) do ) do
with actor_url <- Utils.get_actor(data), with actor_url <- Utils.get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url), {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(actor_url),
{:object_not_found, {:ok, %Activity{} = activity, object}} <- {:object_not_found, {:ok, %Activity{} = activity, object}} <-
{:object_not_found, {:object_not_found,
do_handle_incoming_accept_following(accepted_object, actor) || do_handle_incoming_accept_following(accepted_object, actor) ||
@ -328,7 +330,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
%{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data %{"type" => "Reject", "object" => rejected_object, "actor" => _actor, "id" => id} = data
) do ) do
with actor_url <- Utils.get_actor(data), with actor_url <- Utils.get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url), {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(actor_url),
{:object_not_found, {:ok, activity, object}} <- {:object_not_found, {:ok, activity, object}} <-
{:object_not_found, {:object_not_found,
do_handle_incoming_reject_following(rejected_object, actor) || do_handle_incoming_reject_following(rejected_object, actor) ||
@ -359,7 +361,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
) do ) do
with actor_url <- Utils.get_actor(data), with actor_url <- Utils.get_actor(data),
{:ok, %Actor{id: actor_id, suspended: false} = actor} <- {:ok, %Actor{id: actor_id, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor_url), ActivityPubActor.get_or_fetch_actor_by_url(actor_url),
:ok <- Logger.debug("Fetching contained object"), :ok <- Logger.debug("Fetching contained object"),
{:ok, entity} <- process_announce_data(object, actor), {:ok, entity} <- process_announce_data(object, actor),
:ok <- eventually_create_share(object, entity, actor_id) do :ok <- eventually_create_share(object, entity, actor_id) do
@ -380,7 +382,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
) )
when object_type in ["Person", "Group", "Application", "Service", "Organization"] do when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
with {:ok, %Actor{suspended: false} = old_actor} <- with {:ok, %Actor{suspended: false} = old_actor} <-
ActivityPub.get_or_fetch_actor_by_url(object["id"]), ActivityPubActor.get_or_fetch_actor_by_url(object["id"]),
object_data <- object_data <-
object |> Converter.Actor.as_to_model_data(), object |> Converter.Actor.as_to_model_data(),
{:ok, %Activity{} = activity, %Actor{} = new_actor} <- {:ok, %Activity{} = activity, %Actor{} = new_actor} <-
@ -403,7 +405,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
) do ) do
with actor <- Utils.get_actor(update_data), with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false} = actor} <- {:ok, %Actor{url: actor_url, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:ok, %Event{} = old_event} <- {:ok, %Event{} = old_event} <-
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
object_data <- Converter.Event.as_to_model_data(object), object_data <- Converter.Event.as_to_model_data(object),
@ -428,7 +430,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
with actor <- Utils.get_actor(update_data), with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false}} <- {:ok, %Actor{url: actor_url, suspended: false}} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)}, {:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)},
object_data <- Converter.Comment.as_to_model_data(object), object_data <- Converter.Comment.as_to_model_data(object),
{:ok, old_entity} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), {:ok, old_entity} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
@ -448,7 +450,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
) do ) do
with actor <- Utils.get_actor(update_data), with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false} = actor} <- {:ok, %Actor{url: actor_url, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:ok, %Post{} = old_post} <- {:ok, %Post{} = old_post} <-
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
object_data <- Converter.Post.as_to_model_data(object), object_data <- Converter.Post.as_to_model_data(object),
@ -476,7 +478,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
when type in ["ResourceCollection", "Document"] do when type in ["ResourceCollection", "Document"] do
with actor <- Utils.get_actor(update_data), with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false}} <- {:ok, %Actor{url: actor_url, suspended: false}} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:ok, %Resource{} = old_resource} <- {:ok, %Resource{} = old_resource} <-
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
object_data <- Converter.Resource.as_to_model_data(object), object_data <- Converter.Resource.as_to_model_data(object),
@ -501,7 +503,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
with actor <- Utils.get_actor(update_data), with actor <- Utils.get_actor(update_data),
{:ok, %Actor{url: actor_url, suspended: false} = actor} <- {:ok, %Actor{url: actor_url, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)}, {:origin_check, true} <- {:origin_check, Utils.origin_check?(actor_url, update_data)},
object_data <- Converter.Member.as_to_model_data(object), object_data <- Converter.Member.as_to_model_data(object),
{:ok, old_entity} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), {:ok, old_entity} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
@ -543,7 +545,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
} = data } = data
) do ) do
with actor <- Utils.get_actor(data), with actor <- Utils.get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor), {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:ok, object} <- fetch_obj_helper_as_activity_streams(object_id), {:ok, object} <- fetch_obj_helper_as_activity_streams(object_id),
{:ok, activity, object} <- {:ok, activity, object} <-
ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do ActivityPub.unannounce(actor, object, id, cancelled_activity_id, false) do
@ -561,8 +563,9 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
"id" => id "id" => id
} = _data } = _data
) do ) do
with {:ok, %Actor{domain: nil} = followed} <- ActivityPub.get_or_fetch_actor_by_url(followed), with {:ok, %Actor{domain: nil} = followed} <-
{:ok, %Actor{} = follower} <- ActivityPub.get_or_fetch_actor_by_url(follower), ActivityPubActor.get_or_fetch_actor_by_url(followed),
{:ok, %Actor{} = follower} <- ActivityPubActor.get_or_fetch_actor_by_url(follower),
{:ok, activity, object} <- ActivityPub.unfollow(follower, followed, id, false) do {:ok, activity, object} <- ActivityPub.unfollow(follower, followed, id, false) do
{:ok, activity, object} {:ok, activity, object}
else else
@ -579,7 +582,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
) do ) do
with actor_url <- Utils.get_actor(data), with actor_url <- Utils.get_actor(data),
{:actor, {:ok, %Actor{} = actor}} <- {:actor, {:ok, %Actor{} = actor}} <-
{:actor, ActivityPub.get_or_fetch_actor_by_url(actor_url)}, {:actor, ActivityPubActor.get_or_fetch_actor_by_url(actor_url)},
object_id <- Utils.get_url(object), object_id <- Utils.get_url(object),
{:ok, object} <- is_group_object_gone(object_id), {:ok, object} <- is_group_object_gone(object_id),
{:origin_check, true} <- {:origin_check, true} <-
@ -622,7 +625,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
when type in ["ResourceCollection", "Document"] do when type in ["ResourceCollection", "Document"] do
with actor <- Utils.get_actor(data), with actor <- Utils.get_actor(data),
{:ok, %Actor{url: actor_url, suspended: false} = actor} <- {:ok, %Actor{url: actor_url, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
{:ok, %Resource{} = old_resource} <- {:ok, %Resource{} = old_resource} <-
object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
object_data <- Converter.Resource.as_to_model_data(object), object_data <- Converter.Resource.as_to_model_data(object),
@ -654,7 +657,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
) do ) do
with actor <- Utils.get_actor(data), with actor <- Utils.get_actor(data),
{:ok, %Actor{url: _actor_url, suspended: false} = actor} <- {:ok, %Actor{url: _actor_url, suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor), ActivityPubActor.get_or_fetch_actor_by_url(actor),
object <- Utils.get_url(object), object <- Utils.get_url(object),
{:ok, object} <- ActivityPub.fetch_object_from_url(object), {:ok, object} <- ActivityPub.fetch_object_from_url(object),
{:ok, activity, object} <- {:ok, activity, object} <-
@ -672,7 +675,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
def handle_incoming(%{"type" => "Leave", "object" => object, "actor" => actor} = data) do def handle_incoming(%{"type" => "Leave", "object" => object, "actor" => actor} = data) do
with actor <- Utils.get_actor(data), with actor <- Utils.get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor), {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(actor),
object <- Utils.get_url(object), object <- Utils.get_url(object),
{:ok, object} <- ActivityPub.fetch_object_from_url(object), {:ok, object} <- ActivityPub.fetch_object_from_url(object),
{:ok, activity, object} <- ActivityPub.leave(object, actor, false) do {:ok, activity, object} <- ActivityPub.leave(object, actor, false) do
@ -702,10 +705,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
Logger.info("Handle incoming to invite someone") Logger.info("Handle incoming to invite someone")
with {:ok, %Actor{} = actor} <- with {:ok, %Actor{} = actor} <-
data |> Utils.get_actor() |> ActivityPub.get_or_fetch_actor_by_url(), data |> Utils.get_actor() |> ActivityPubActor.get_or_fetch_actor_by_url(),
{:ok, object} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(), {:ok, object} <- object |> Utils.get_url() |> ActivityPub.fetch_object_from_url(),
{:ok, %Actor{} = target} <- {:ok, %Actor{} = target} <-
target |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url(), target |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url(),
{:ok, activity, %Member{} = member} <- {:ok, activity, %Member{} = member} <-
ActivityPub.invite(object, actor, target, false, %{url: id}) do ActivityPub.invite(object, actor, target, false, %{url: id}) do
{:ok, activity, member} {:ok, activity, member}
@ -718,10 +721,10 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
Logger.info("Handle incoming to remove a member from a group") Logger.info("Handle incoming to remove a member from a group")
with {:ok, %Actor{id: moderator_id} = moderator} <- with {:ok, %Actor{id: moderator_id} = moderator} <-
data |> Utils.get_actor() |> ActivityPub.get_or_fetch_actor_by_url(), data |> Utils.get_actor() |> ActivityPubActor.get_or_fetch_actor_by_url(),
{:ok, person_id} <- get_remove_object(object), {:ok, person_id} <- get_remove_object(object),
{:ok, %Actor{type: :Group, id: group_id} = group} <- {:ok, %Actor{type: :Group, id: group_id} = group} <-
origin |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url(), origin |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url(),
{:is_admin, {:ok, %Member{role: role}}} {:is_admin, {:ok, %Member{role: role}}}
when role in [:moderator, :administrator, :creator] <- when role in [:moderator, :administrator, :creator] <-
{:is_admin, Actors.get_member(moderator_id, group_id)}, {:is_admin, Actors.get_member(moderator_id, group_id)},

View file

@ -14,6 +14,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay} alias Mobilizon.Federation.ActivityPub.{Activity, Federator, Relay}
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Types.Ownable alias Mobilizon.Federation.ActivityPub.Types.Ownable
alias Mobilizon.Federation.ActivityStream.Converter alias Mobilizon.Federation.ActivityStream.Converter
alias Mobilizon.Federation.HTTPSignatures alias Mobilizon.Federation.HTTPSignatures
@ -175,7 +176,7 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
@spec remote_actors(list(String.t())) :: list(Actor.t()) @spec remote_actors(list(String.t())) :: list(Actor.t())
def remote_actors(recipients) do def remote_actors(recipients) do
recipients recipients
|> Enum.map(fn url -> ActivityPub.get_or_fetch_actor_by_url(url) end) |> Enum.map(fn url -> ActivityPubActor.get_or_fetch_actor_by_url(url) end)
|> Enum.map(fn {status, actor} -> |> Enum.map(fn {status, actor} ->
case status do case status do
:ok -> :ok ->
@ -259,8 +260,9 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
are_same_origin?(id, actor) are_same_origin?(id, actor)
end end
def origin_check?(_id, %{"type" => type} = _params) when type in ["Actor", "Person", "Group"], def origin_check?(id, %{"type" => type, "id" => actor_id} = _params)
do: true when type in ["Actor", "Person", "Group"],
do: id == actor_id
def origin_check?(_id, %{"actor" => nil} = _args), do: false def origin_check?(_id, %{"actor" => nil} = _args), do: false

View file

@ -8,7 +8,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Discussion do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Discussions.Discussion alias Mobilizon.Discussions.Discussion
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Federation.ActivityStream.Converter.Discussion, as: DiscussionConverter alias Mobilizon.Federation.ActivityStream.Converter.Discussion, as: DiscussionConverter
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
@ -49,10 +49,10 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Discussion do
def as_to_model_data(%{"type" => "Note", "name" => name} = object) when not is_nil(name) do def as_to_model_data(%{"type" => "Note", "name" => name} = object) when not is_nil(name) do
with creator_url <- Map.get(object, "actor"), with creator_url <- Map.get(object, "actor"),
{:ok, %Actor{id: creator_id, suspended: false}} <- {:ok, %Actor{id: creator_id, suspended: false}} <-
ActivityPub.get_or_fetch_actor_by_url(creator_url), ActivityPubActor.get_or_fetch_actor_by_url(creator_url),
actor_url <- Map.get(object, "attributedTo"), actor_url <- Map.get(object, "attributedTo"),
{:ok, %Actor{id: actor_id, suspended: false}} <- {:ok, %Actor{id: actor_id, suspended: false}} <-
ActivityPub.get_or_fetch_actor_by_url(actor_url) do ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
%{ %{
title: name, title: name,
actor_id: actor_id, actor_id: actor_id,

View file

@ -14,7 +14,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
alias Mobilizon.Events.Event alias Mobilizon.Events.Event
alias Mobilizon.Reports.Report alias Mobilizon.Reports.Report
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Relay alias Mobilizon.Federation.ActivityPub.Relay
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
@ -65,10 +65,11 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Flag do
@spec as_to_model(map) :: map @spec as_to_model(map) :: map
def as_to_model(%{"object" => objects} = object) do def as_to_model(%{"object" => objects} = object) do
with {:ok, %Actor{} = reporter} <- ActivityPub.get_or_fetch_actor_by_url(object["actor"]), with {:ok, %Actor{} = reporter} <-
ActivityPubActor.get_or_fetch_actor_by_url(object["actor"]),
%Actor{} = reported <- %Actor{} = reported <-
Enum.reduce_while(objects, nil, fn url, _ -> Enum.reduce_while(objects, nil, fn url, _ ->
case ActivityPub.get_or_fetch_actor_by_url(url) do case ActivityPubActor.get_or_fetch_actor_by_url(url) do
{:ok, %Actor{} = actor} -> {:ok, %Actor{} = actor} ->
{:halt, actor} {:halt, actor}

View file

@ -8,7 +8,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Member do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Actors.Member, as: MemberModel alias Mobilizon.Actors.Member, as: MemberModel
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Utils alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
@ -53,5 +53,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Member do
@spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()} @spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()}
defp get_actor(nil), do: {:error, "nil property found for actor data"} defp get_actor(nil), do: {:error, "nil property found for actor data"}
defp get_actor(actor), do: actor |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url()
defp get_actor(actor),
do: actor |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url()
end end

View file

@ -6,7 +6,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
internal one, and back. internal one, and back.
""" """
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Audience, Utils} alias Mobilizon.Federation.ActivityPub.{Audience, Utils}
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
@ -91,7 +91,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Post do
@spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()} @spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()}
defp get_actor(nil), do: {:error, "nil property found for actor data"} defp get_actor(nil), do: {:error, "nil property found for actor data"}
defp get_actor(actor), do: actor |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url()
defp get_actor(actor),
do: actor |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url()
defp to_date(nil), do: nil defp to_date(nil), do: nil
defp to_date(%DateTime{} = date), do: DateTime.to_iso8601(date) defp to_date(%DateTime{} = date), do: DateTime.to_iso8601(date)

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Resource do
""" """
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Utils alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Resources alias Mobilizon.Resources
@ -88,7 +89,9 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Resource do
@spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()} @spec get_actor(String.t() | map() | nil) :: {:ok, Actor.t()} | {:error, String.t()}
defp get_actor(nil), do: {:error, "nil property found for actor data"} defp get_actor(nil), do: {:error, "nil property found for actor data"}
defp get_actor(actor), do: actor |> Utils.get_url() |> ActivityPub.get_or_fetch_actor_by_url()
defp get_actor(actor),
do: actor |> Utils.get_url() |> ActivityPubActor.get_or_fetch_actor_by_url()
defp get_context(%Resource{parent_id: nil, actor: %Actor{resources_url: resources_url}}), defp get_context(%Resource{parent_id: nil, actor: %Actor{resources_url: resources_url}}),
do: resources_url do: resources_url

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Todo do
""" """
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Todos alias Mobilizon.Todos
alias Mobilizon.Todos.{Todo, TodoList} alias Mobilizon.Todos.{Todo, TodoList}
@ -51,7 +52,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Todo do
%{"type" => "Todo", "actor" => actor_url, "todoList" => todo_list_url} = object %{"type" => "Todo", "actor" => actor_url, "todoList" => todo_list_url} = object
) do ) do
with {:ok, %Actor{id: creator_id} = _creator} <- with {:ok, %Actor{id: creator_id} = _creator} <-
ActivityPub.get_or_fetch_actor_by_url(actor_url), ActivityPubActor.get_or_fetch_actor_by_url(actor_url),
{:todo_list, %TodoList{id: todo_list_id}} <- {:todo_list, %TodoList{id: todo_list_id}} <-
{:todo_list, Todos.get_todo_list_by_url(todo_list_url)} do {:todo_list, Todos.get_todo_list_by_url(todo_list_url)} do
%{ %{

View file

@ -6,7 +6,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.TodoList do
internal one, and back. internal one, and back.
""" """
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityStream.{Converter, Convertible} alias Mobilizon.Federation.ActivityStream.{Converter, Convertible}
alias Mobilizon.Todos.TodoList alias Mobilizon.Todos.TodoList
@ -39,7 +39,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.TodoList do
@impl Converter @impl Converter
@spec as_to_model_data(map) :: {:ok, map} | {:error, any()} @spec as_to_model_data(map) :: {:ok, map} | {:error, any()}
def as_to_model_data(%{"type" => "TodoList", "actor" => actor_url} = object) do def as_to_model_data(%{"type" => "TodoList", "actor" => actor_url} = object) do
case ActivityPub.get_or_fetch_actor_by_url(actor_url) do case ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
{:ok, %Actor{type: :Group, id: group_id} = _group} -> {:ok, %Actor{type: :Group, id: group_id} = _group} ->
%{ %{
title: object["name"], title: object["name"],

View file

@ -10,7 +10,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
alias Mobilizon.Mention alias Mobilizon.Mention
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter alias Mobilizon.Federation.ActivityStream.Converter.Media, as: MediaConverter
alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Endpoint
@ -114,7 +114,8 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
@spec create_mention(map(), list()) :: list() @spec create_mention(map(), list()) :: list()
defp create_mention(mention, acc) when is_map(mention) do defp create_mention(mention, acc) when is_map(mention) do
with true <- mention["type"] == "Mention", with true <- mention["type"] == "Mention",
{:ok, %Actor{id: actor_id}} <- ActivityPub.get_or_fetch_actor_by_url(mention["href"]) do {:ok, %Actor{id: actor_id}} <-
ActivityPubActor.get_or_fetch_actor_by_url(mention["href"]) do
acc ++ [%{actor_id: actor_id}] acc ++ [%{actor_id: actor_id}]
else else
_err -> _err ->
@ -169,7 +170,7 @@ defmodule Mobilizon.Federation.ActivityStream.Converter.Utils do
@spec fetch_actor(String.t()) :: Actor.t() @spec fetch_actor(String.t()) :: Actor.t()
defp fetch_actor(actor_url) do defp fetch_actor(actor_url) do
with {:ok, %Actor{suspended: false} = actor} <- with {:ok, %Actor{suspended: false} = actor} <-
ActivityPub.get_or_fetch_actor_by_url(actor_url) do ActivityPubActor.get_or_fetch_actor_by_url(actor_url) do
actor actor
end end
end end

View file

@ -12,7 +12,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
require Logger require Logger
@ -53,7 +53,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
{:ok, String.t()} {:ok, String.t()}
| {:error, :actor_fetch_error | :pem_decode_error | :actor_not_fetchable} | {:error, :actor_fetch_error | :pem_decode_error | :actor_not_fetchable}
defp get_public_key_for_url(url) do defp get_public_key_for_url(url) do
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url), with {:ok, %Actor{keys: keys}} <- ActivityPubActor.get_or_fetch_actor_by_url(url),
{:ok, public_key} <- prepare_public_key(keys) do {:ok, public_key} <- prepare_public_key(keys) do
{:ok, public_key} {:ok, public_key}
else else
@ -90,7 +90,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),
actor_id <- key_id_to_actor_url(kid), actor_id <- key_id_to_actor_url(kid),
:ok <- Logger.debug("Refetching public key for #{actor_id}"), :ok <- Logger.debug("Refetching public key for #{actor_id}"),
{:ok, _actor} <- ActivityPub.make_actor_from_url(actor_id), {:ok, _actor} <- ActivityPubActor.make_actor_from_url(actor_id),
{:ok, public_key} <- get_public_key_for_url(actor_id) do {:ok, public_key} <- get_public_key_for_url(actor_id) do
{:ok, public_key} {:ok, public_key}
end end

View file

@ -10,7 +10,7 @@ defmodule Mobilizon.Federation.WebFinger do
alias Mobilizon.Actors alias Mobilizon.Actors
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.WebFinger.XmlBuilder alias Mobilizon.Federation.WebFinger.XmlBuilder
alias Mobilizon.Service.HTTP.{HostMetaClient, WebfingerClient} alias Mobilizon.Service.HTTP.{HostMetaClient, WebfingerClient}
alias Mobilizon.Web.Endpoint alias Mobilizon.Web.Endpoint
@ -56,7 +56,7 @@ defmodule Mobilizon.Federation.WebFinger do
{:ok, represent_actor(actor, "JSON")} {:ok, represent_actor(actor, "JSON")}
else else
_e -> _e ->
case ActivityPub.get_or_fetch_actor_by_url(resource) do case ActivityPubActor.get_or_fetch_actor_by_url(resource) do
{:ok, %Actor{} = actor} when not is_nil(actor) -> {:ok, %Actor{} = actor} when not is_nil(actor) ->
{:ok, represent_actor(actor, "JSON")} {:ok, represent_actor(actor, "JSON")}

View file

@ -10,6 +10,7 @@ defmodule Mobilizon.GraphQL.API.Search do
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
require Logger require Logger
@ -92,7 +93,7 @@ defmodule Mobilizon.GraphQL.API.Search do
# If the search string is an username # If the search string is an username
@spec process_from_username(String.t()) :: Page.t() @spec process_from_username(String.t()) :: Page.t()
defp process_from_username(search) do defp process_from_username(search) do
case ActivityPub.find_or_make_actor_from_nickname(search) do case ActivityPubActor.find_or_make_actor_from_nickname(search) do
{:ok, actor} -> {:ok, actor} ->
%Page{total: 1, elements: [actor]} %Page{total: 1, elements: [actor]}

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
alias Mobilizon.{Actors, Events, Users} alias Mobilizon.{Actors, Events, Users}
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.GraphQL.API alias Mobilizon.GraphQL.API
alias Mobilizon.Users.User alias Mobilizon.Users.User
alias Mobilizon.Web.Upload alias Mobilizon.Web.Upload
@ -27,7 +28,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
} }
) do ) do
with {:group, {:ok, %Actor{id: group_id, suspended: false} = group}} <- with {:group, {:ok, %Actor{id: group_id, suspended: false} = group}} <-
{:group, ActivityPub.find_or_make_group_from_nickname(name)}, {:group, ActivityPubActor.find_or_make_group_from_nickname(name)},
{:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)}, {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do {:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
{:ok, group} {:ok, group}
@ -45,7 +46,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
def find_group(_parent, %{preferred_username: name}, _resolution) do def find_group(_parent, %{preferred_username: name}, _resolution) do
with {:ok, %Actor{suspended: false} = actor} <- with {:ok, %Actor{suspended: false} = actor} <-
ActivityPub.find_or_make_group_from_nickname(name), ActivityPubActor.find_or_make_group_from_nickname(name),
%Actor{} = actor <- restrict_fields_for_non_member_request(actor) do %Actor{} = actor <- restrict_fields_for_non_member_request(actor) do
{:ok, actor} {:ok, actor}
else else

View file

@ -7,6 +7,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
alias Mobilizon.{Actors, Users} alias Mobilizon.{Actors, Users}
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Refresher alias Mobilizon.Federation.ActivityPub.Refresher
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Users.User alias Mobilizon.Users.User
@ -70,7 +71,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
target_actor_username |> String.trim() |> String.trim_leading("@"), target_actor_username |> String.trim() |> String.trim_leading("@"),
{:target_actor_username, {:ok, %Actor{id: target_actor_id} = target_actor}} <- {:target_actor_username, {:ok, %Actor{id: target_actor_id} = target_actor}} <-
{:target_actor_username, {:target_actor_username,
ActivityPub.find_or_make_actor_from_nickname(target_actor_username)}, ActivityPubActor.find_or_make_actor_from_nickname(target_actor_username)},
{:existant, true} <- {:existant, true} <-
{:existant, check_member_not_existant_or_rejected(target_actor_id, group.id)}, {:existant, check_member_not_existant_or_rejected(target_actor_id, group.id)},
{:ok, _activity, %Member{} = member} <- ActivityPub.invite(group, actor, target_actor) do {:ok, _activity, %Member{} = member} <- ActivityPub.invite(group, actor, target_actor) do

View file

@ -13,6 +13,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
import Mobilizon.Web.Gettext import Mobilizon.Web.Gettext
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
require Logger require Logger
alias Mobilizon.Web.Upload alias Mobilizon.Web.Upload
@ -39,7 +40,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
context: %{current_user: %User{} = user} context: %{current_user: %User{} = user}
}) do }) do
with {:ok, %Actor{id: actor_id} = actor} <- with {:ok, %Actor{id: actor_id} = actor} <-
ActivityPub.find_or_make_actor_from_nickname(preferred_username), ActivityPubActor.find_or_make_actor_from_nickname(preferred_username),
{:own, {:is_owned, _}} <- {:own, User.owns_actor(user, actor_id)} do {:own, {:is_owned, _}} <- {:own, User.owns_actor(user, actor_id)} do
{:ok, actor} {:ok, actor}
else else

View file

@ -4,7 +4,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
""" """
use Mix.Task use Mix.Task
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Storage.Repo alias Mobilizon.Storage.Repo
import Ecto.Query import Ecto.Query
import Mix.Tasks.Mobilizon.Common import Mix.Tasks.Mobilizon.Common
@ -65,7 +65,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
def run([preferred_username]) do def run([preferred_username]) do
start_mobilizon() start_mobilizon()
case ActivityPub.make_actor_from_nickname(preferred_username) do case ActivityPubActor.make_actor_from_nickname(preferred_username) do
{:ok, %Actor{}} -> {:ok, %Actor{}} ->
shell_info(""" shell_info("""
Actor #{preferred_username} refreshed Actor #{preferred_username} refreshed
@ -89,7 +89,7 @@ defmodule Mix.Tasks.Mobilizon.Actors.Refresh do
@spec make_actor(String.t(), boolean()) :: any() @spec make_actor(String.t(), boolean()) :: any()
defp make_actor(username, verbose) do defp make_actor(username, verbose) do
ActivityPub.make_actor_from_nickname(username) ActivityPubActor.make_actor_from_nickname(username)
rescue rescue
_ -> _ ->
if verbose do if verbose do

View file

@ -10,6 +10,7 @@ defmodule Mobilizon.Web.ActivityPubController do
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Federator, Utils} alias Mobilizon.Federation.ActivityPub.{Federator, Utils}
alias Mobilizon.Web.ActivityPub.ActorView alias Mobilizon.Web.ActivityPub.ActorView
@ -108,7 +109,7 @@ defmodule Mobilizon.Web.ActivityPubController do
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"name" => preferred_username} = params) do def inbox(%{assigns: %{valid_signature: true}} = conn, %{"name" => preferred_username} = params) do
with %Actor{url: recipient_url} = recipient <- with %Actor{url: recipient_url} = recipient <-
Actors.get_local_actor_by_name(preferred_username), Actors.get_local_actor_by_name(preferred_username),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(params["actor"]), {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(params["actor"]),
true <- Utils.recipient_in_message(recipient, actor, params), true <- Utils.recipient_in_message(recipient, actor, params),
params <- Utils.maybe_splice_recipient(recipient_url, params) do params <- Utils.maybe_splice_recipient(recipient_url, params) do
Federator.enqueue(:incoming_ap_doc, params) Federator.enqueue(:incoming_ap_doc, params)

View file

@ -12,7 +12,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Utils alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Federation.HTTPSignatures.Signature
@ -34,7 +34,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
@spec actor_from_key_id(Plug.Conn.t()) :: Actor.t() | nil @spec actor_from_key_id(Plug.Conn.t()) :: Actor.t() | nil
defp actor_from_key_id(conn) do defp actor_from_key_id(conn) do
with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(key_actor_id) do {:ok, %Actor{} = actor} <- ActivityPubActor.get_or_fetch_actor_by_url(key_actor_id) do
actor actor
else else
_ -> _ ->

View file

@ -11,13 +11,12 @@ defmodule Mobilizon.Federation.ActivityPubTest do
import Mox import Mox
import Mobilizon.Factory import Mobilizon.Factory
alias Mobilizon.{Actors, Discussions, Events} alias Mobilizon.{Discussions, Events}
alias Mobilizon.Actors.Actor
alias Mobilizon.Resources.Resource alias Mobilizon.Resources.Resource
alias Mobilizon.Todos.{Todo, TodoList} alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Relay, Utils} alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.HTTPSignatures.Signature alias Mobilizon.Federation.HTTPSignatures.Signature
alias Mobilizon.Service.HTTP.ActivityPub.Mock alias Mobilizon.Service.HTTP.ActivityPub.Mock
@ -40,116 +39,6 @@ defmodule Mobilizon.Federation.ActivityPubTest do
end end
end end
describe "fetching actor from its url" do
test "returns an actor from nickname" do
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
_actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
end
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
_actor} = ActivityPub.make_actor_from_nickname("tcit@framapiaf.org")
end
end
@actor_url "https://framapiaf.org/users/tcit"
test "returns an actor from url" do
# Initial fetch
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
# Unlisted because discoverable is not present in the JSON payload
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted}} =
ActivityPub.get_or_fetch_actor_by_url(@actor_url)
end
# Fetch uses cache if Actors.needs_update? returns false
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> false end
]},
{ActivityPub, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPub.get_or_fetch_actor_by_url(@actor_url)
assert_called(Actors.needs_update?(:_))
refute called(ActivityPub.make_actor_from_url(@actor_url, false))
end
# Fetch doesn't use cache if Actors.needs_update? returns true
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> true end
]},
{ActivityPub, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPub.get_or_fetch_actor_by_url(@actor_url)
assert_called(ActivityPub.get_or_fetch_actor_by_url(@actor_url))
assert_called(Actors.get_actor_by_url(@actor_url, false))
assert_called(Actors.needs_update?(:_))
assert_called(ActivityPub.make_actor_from_url(@actor_url, false))
end
end
@public_url "https://www.w3.org/ns/activitystreams#Public"
test "activitystreams#Public uri returns Relay actor" do
assert ActivityPub.get_or_fetch_actor_by_url(@public_url) == {:ok, Relay.get_actor()}
end
end
describe "create activities" do
# test "removes doubled 'to' recipients" do
# actor = insert(:actor)
#
# {:ok, activity, _} =
# ActivityPub.create(%{
# to: ["user1", "user1", "user2"],
# actor: actor,
# context: "",
# object: %{}
# })
#
# assert activity.data["to"] == ["user1", "user2"]
# assert activity.actor == actor.url
# assert activity.recipients == ["user1", "user2"]
# end
end
describe "fetching an" do describe "fetching an" do
test "object by url" do test "object by url" do
url = "https://framapiaf.org/users/Framasoft/statuses/102093631881522097" url = "https://framapiaf.org/users/Framasoft/statuses/102093631881522097"

View file

@ -0,0 +1,120 @@
defmodule Mobilizon.Federation.ActivityPub.ActorTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Mobilizon.DataCase
import Mock
alias Mobilizon.Actors
alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay}
describe "fetching actor from its url" do
test "returns an actor from nickname" do
use_cassette "activity_pub/fetch_tcit@framapiaf.org" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :public} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
use_cassette "activity_pub/fetch_tcit@framapiaf.org_not_discoverable" do
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted} =
_actor} = ActivityPubActor.make_actor_from_nickname("tcit@framapiaf.org")
end
end
@actor_url "https://framapiaf.org/users/tcit"
test "returns an actor from url" do
# Initial fetch
use_cassette "activity_pub/fetch_framapiaf.org_users_tcit" do
# Unlisted because discoverable is not present in the JSON payload
assert {:ok,
%Actor{preferred_username: "tcit", domain: "framapiaf.org", visibility: :unlisted}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
end
# Fetch uses cache if Actors.needs_update? returns false
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> false end
]},
{ActivityPubActor, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
assert_called(Actors.needs_update?(:_))
refute called(ActivityPubActor.make_actor_from_url(@actor_url, false))
end
# Fetch doesn't use cache if Actors.needs_update? returns true
with_mocks([
{Actors, [:passthrough],
[
get_actor_by_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end,
needs_update?: fn _ -> true end
]},
{ActivityPubActor, [:passthrough],
make_actor_from_url: fn @actor_url, false ->
{:ok,
%Actor{
preferred_username: "tcit",
domain: "framapiaf.org"
}}
end}
]) do
assert {:ok, %Actor{preferred_username: "tcit", domain: "framapiaf.org"}} =
ActivityPubActor.get_or_fetch_actor_by_url(@actor_url)
assert_called(ActivityPubActor.get_or_fetch_actor_by_url(@actor_url))
assert_called(Actors.get_actor_by_url(@actor_url, false))
assert_called(Actors.needs_update?(:_))
assert_called(ActivityPubActor.make_actor_from_url(@actor_url, false))
end
end
test "handles remote actor being deleted" do
with_mocks([
{Fetcher, [:passthrough],
fetch_and_prepare_actor_from_url: fn @actor_url ->
{:error, :actor_deleted}
end}
]) do
assert match?(
{:error, :actor_deleted},
ActivityPubActor.make_actor_from_url(@actor_url, false)
)
assert_called(Fetcher.fetch_and_prepare_actor_from_url(@actor_url))
end
end
@public_url "https://www.w3.org/ns/activitystreams#Public"
test "activitystreams#Public uri returns Relay actor" do
assert ActivityPubActor.get_or_fetch_actor_by_url(@public_url) == {:ok, Relay.get_actor()}
end
end
end

View file

@ -3,7 +3,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney use ExVCR.Mock, adapter: ExVCR.Adapter.Hackney
use Oban.Testing, repo: Mobilizon.Storage.Repo use Oban.Testing, repo: Mobilizon.Storage.Repo
import Mobilizon.Factory import Mobilizon.Factory
import ExUnit.CaptureLog
import Mox import Mox
alias Mobilizon.{Actors, Discussions, Events, Posts, Resources} alias Mobilizon.{Actors, Discussions, Events, Posts, Resources}
@ -78,7 +77,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
data data
|> Map.put("object", object) |> Map.put("object", object)
:error = Transmogrifier.handle_incoming(data) {:error, :unknown_actor} = Transmogrifier.handle_incoming(data)
assert Discussions.get_comment_from_url(comment.url) assert Discussions.get_comment_from_url(comment.url)
end end
@ -119,13 +118,14 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
test "it fails for incoming actor deletes with spoofed origin" do test "it fails for incoming actor deletes with spoofed origin" do
%{url: url} = insert(:actor) %{url: url} = insert(:actor)
deleted_actor_url = "https://framapiaf.org/users/admin"
data = data =
File.read!("test/fixtures/mastodon-delete-user.json") File.read!("test/fixtures/mastodon-delete-user.json")
|> Jason.decode!() |> Jason.decode!()
|> Map.put("actor", url) |> Map.put("actor", url)
deleted_actor_url = "https://framapiaf.org/users/admin"
deleted_actor_data = deleted_actor_data =
File.read!("test/fixtures/mastodon-actor.json") File.read!("test/fixtures/mastodon-actor.json")
|> Jason.decode!() |> Jason.decode!()
@ -137,9 +137,7 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.DeleteTest do
{:ok, %Tesla.Env{status: 200, body: deleted_actor_data}} {:ok, %Tesla.Env{status: 200, body: deleted_actor_data}}
end) end)
assert capture_log(fn -> assert :error == Transmogrifier.handle_incoming(data)
assert :error == Transmogrifier.handle_incoming(data)
end) =~ "Object origin check failed"
assert Actors.get_actor_by_url(url) assert Actors.get_actor_by_url(url)
end end

View file

@ -7,7 +7,6 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier.PostsTest do
alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Activity, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
alias Mobilizon.Posts.Post alias Mobilizon.Posts.Post
alias Mobilizon.Service.HTTP.ActivityPub.Mock
describe "handle incoming posts" do describe "handle incoming posts" do
setup :verify_on_exit! setup :verify_on_exit!

View file

@ -21,6 +21,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
alias Mobilizon.Todos.{Todo, TodoList} alias Mobilizon.Todos.{Todo, TodoList}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Federation.ActivityPub.Utils alias Mobilizon.Federation.ActivityPub.Utils
alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Transmogrifier} alias Mobilizon.Federation.ActivityPub.{Activity, Relay, Transmogrifier}
alias Mobilizon.Federation.ActivityStream.Convertible alias Mobilizon.Federation.ActivityStream.Convertible
@ -89,7 +90,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
preferred_username: "member" preferred_username: "member"
) )
with_mock ActivityPub, [:passthrough], with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url -> get_or_fetch_actor_by_url: fn url ->
case url do case url do
^group_url -> {:ok, group} ^group_url -> {:ok, group}
@ -168,7 +169,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
group = insert(:group, domain: "morebilizon.com", url: @mobilizon_group_url) group = insert(:group, domain: "morebilizon.com", url: @mobilizon_group_url)
%Actor{url: actor_url} = actor = insert(:actor) %Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough], with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url -> get_or_fetch_actor_by_url: fn url ->
case url do case url do
@mobilizon_group_url -> {:ok, group} @mobilizon_group_url -> {:ok, group}
@ -198,7 +199,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
test "it accepts incoming todo lists and handles group being not found" do test "it accepts incoming todo lists and handles group being not found" do
%Actor{url: actor_url} = actor = insert(:actor) %Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough], with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url -> get_or_fetch_actor_by_url: fn url ->
case url do case url do
@mobilizon_group_url -> {:error, "Not found"} @mobilizon_group_url -> {:error, "Not found"}
@ -274,7 +275,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
group = insert(:group, domain: "morebilizon.com", url: @mobilizon_group_url) group = insert(:group, domain: "morebilizon.com", url: @mobilizon_group_url)
%Actor{url: actor_url} = actor = insert(:actor) %Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough], with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url -> get_or_fetch_actor_by_url: fn url ->
case url do case url do
@mobilizon_group_url -> {:ok, group} @mobilizon_group_url -> {:ok, group}
@ -304,7 +305,7 @@ defmodule Mobilizon.Federation.ActivityPub.TransmogrifierTest do
test "it accepts incoming todo lists and handles group being not found" do test "it accepts incoming todo lists and handles group being not found" do
%Actor{url: actor_url} = actor = insert(:actor) %Actor{url: actor_url} = actor = insert(:actor)
with_mock ActivityPub, [:passthrough], with_mock ActivityPubActor, [:passthrough],
get_or_fetch_actor_by_url: fn url -> get_or_fetch_actor_by_url: fn url ->
case url do case url do
@mobilizon_group_url -> {:error, "Not found"} @mobilizon_group_url -> {:error, "Not found"}

View file

@ -12,14 +12,15 @@ defmodule Mobilizon.GraphQL.API.SearchTest do
alias Mobilizon.GraphQL.API.Search alias Mobilizon.GraphQL.API.Search
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
test "search an user by username" do test "search an user by username" do
with_mock ActivityPub, with_mock ActivityPubActor,
find_or_make_actor_from_nickname: fn "toto@domain.tld" -> {:ok, %Actor{id: 42}} end do find_or_make_actor_from_nickname: fn "toto@domain.tld" -> {:ok, %Actor{id: 42}} end do
assert {:ok, %Page{total: 1, elements: [%Actor{id: 42}]}} == assert {:ok, %Page{total: 1, elements: [%Actor{id: 42}]}} ==
Search.search_actors(%{term: "toto@domain.tld"}, 1, 10, :Person) Search.search_actors(%{term: "toto@domain.tld"}, 1, 10, :Person)
assert_called(ActivityPub.find_or_make_actor_from_nickname("toto@domain.tld")) assert_called(ActivityPubActor.find_or_make_actor_from_nickname("toto@domain.tld"))
end end
end end

View file

@ -13,7 +13,7 @@ defmodule Mobilizon.ActorsTest do
alias Mobilizon.Service.Workers alias Mobilizon.Service.Workers
alias Mobilizon.Storage.Page alias Mobilizon.Storage.Page
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
alias Mobilizon.Web.Upload.Uploader alias Mobilizon.Web.Upload.Uploader
@ -106,7 +106,7 @@ defmodule Mobilizon.ActorsTest do
preferred_username: preferred_username, preferred_username: preferred_username,
domain: domain, domain: domain,
avatar: %FileModel{name: picture_name} = _picture avatar: %FileModel{name: picture_name} = _picture
} = _actor} = ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) } = _actor} = ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url)
assert picture_name == "a28c50ce5f2b13fd.jpg" assert picture_name == "a28c50ce5f2b13fd.jpg"
@ -156,7 +156,8 @@ defmodule Mobilizon.ActorsTest do
test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do test "get_actor_by_name_with_preload!/1 returns the remote actor with its organized events" do
use_cassette "actors/remote_actor_mastodon_tcit" do use_cassette "actors/remote_actor_mastodon_tcit" do
with {:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do with {:ok, %Actor{} = actor} <-
ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
assert Actors.get_actor_by_name_with_preload( assert Actors.get_actor_by_name_with_preload(
"#{actor.preferred_username}@#{actor.domain}" "#{actor.preferred_username}@#{actor.domain}"
).organized_events == [] ).organized_events == []
@ -186,7 +187,7 @@ defmodule Mobilizon.ActorsTest do
%{actor: %Actor{id: actor_id}} do %{actor: %Actor{id: actor_id}} do
use_cassette "actors/remote_actor_mastodon_tcit" do use_cassette "actors/remote_actor_mastodon_tcit" do
with {:ok, %Actor{id: actor2_id}} <- with {:ok, %Actor{id: actor2_id}} <-
ActivityPub.get_or_fetch_actor_by_url(@remote_account_url) do ActivityPubActor.get_or_fetch_actor_by_url(@remote_account_url) do
%Page{total: 2, elements: actors} = %Page{total: 2, elements: actors} =
Actors.build_actors_by_username_or_name_page("tcit", Actors.build_actors_by_username_or_name_page("tcit",
actor_type: [:Person], actor_type: [:Person],