85e4715412
Also change ActorView to send proper HTTP error codes Signed-off-by: Thomas Citharel <tcit@tcit.fr>
213 lines
7 KiB
Elixir
213 lines
7 KiB
Elixir
# Portions of this file are derived from Pleroma:
|
|
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social>
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Upstream: https://git.pleroma.social/pleroma/pleroma/blob/develop/lib/pleroma/web/activity_pub/relay.ex
|
|
|
|
defmodule Mobilizon.Federation.ActivityPub.Relay do
|
|
@moduledoc """
|
|
Handles following and unfollowing relays and instances.
|
|
"""
|
|
|
|
alias Mobilizon.Actors
|
|
alias Mobilizon.Actors.{Actor, Follower}
|
|
|
|
alias Mobilizon.Federation.ActivityPub.{Actions, Activity, Transmogrifier}
|
|
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
|
alias Mobilizon.Federation.WebFinger
|
|
alias Mobilizon.GraphQL.API.Follows
|
|
alias Mobilizon.Service.Workers.Background
|
|
import Mobilizon.Federation.ActivityPub.Utils, only: [create_full_domain_string: 1]
|
|
|
|
require Logger
|
|
|
|
def init do
|
|
# Wait for everything to settle.
|
|
Process.sleep(1000 * 5)
|
|
relay = get_actor()
|
|
|
|
unless Regex.match?(~r/BEGIN RSA PRIVATE KEY/, relay.keys) do
|
|
{:ok, _relay} = Actors.actor_key_rotation(relay)
|
|
end
|
|
|
|
relay
|
|
end
|
|
|
|
@spec get_actor() :: Actor.t() | no_return
|
|
def get_actor do
|
|
case Actors.get_or_create_internal_actor("relay") do
|
|
{:ok, %Actor{} = actor} ->
|
|
actor
|
|
|
|
{:error, %Ecto.Changeset{} = _err} ->
|
|
raise("Relay actor not found")
|
|
end
|
|
end
|
|
|
|
@spec follow(String.t()) ::
|
|
{:ok, Activity.t(), Follower.t()} | {:error, atom()} | {:error, String.t()}
|
|
def follow(address) do
|
|
%Actor{} = local_actor = get_actor()
|
|
|
|
with {:ok, target_instance} <- fetch_actor(address),
|
|
{:ok, %Actor{id: target_actor_id} = target_actor} <-
|
|
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
|
|
{:ok, activity, follow} <- Follows.follow(local_actor, target_actor) do
|
|
Logger.info("Relay: followed instance #{target_instance}; id=#{activity.data["id"]}")
|
|
|
|
Background.enqueue("refresh_profile", %{
|
|
"actor_id" => target_actor_id
|
|
})
|
|
|
|
Logger.info("Relay: schedule refreshing instance #{target_instance} after follow")
|
|
|
|
{:ok, activity, follow}
|
|
else
|
|
{:error, :person_no_follow} ->
|
|
Logger.warning("Only group and instances can be followed")
|
|
{:error, :person_no_follow}
|
|
|
|
{:error, e} ->
|
|
Logger.warning("Error while following remote instance: #{inspect(e)}")
|
|
{:error, e}
|
|
end
|
|
end
|
|
|
|
@spec unfollow(String.t()) ::
|
|
{:ok, Activity.t(), Follower.t()} | {:error, atom()} | {:error, String.t()}
|
|
def unfollow(address) do
|
|
%Actor{} = local_actor = get_actor()
|
|
|
|
with {:ok, target_instance} <- fetch_actor(address),
|
|
{:ok, %Actor{} = target_actor} <-
|
|
ActivityPubActor.get_or_fetch_actor_by_url(target_instance),
|
|
{:ok, activity, follow} <- Follows.unfollow(local_actor, target_actor) do
|
|
Logger.info("Relay: unfollowed instance #{target_instance}: id=#{activity.data["id"]}")
|
|
{:ok, activity, follow}
|
|
else
|
|
{:error, e} ->
|
|
Logger.warning("Error while unfollowing remote instance: #{inspect(e)}")
|
|
{:error, e}
|
|
end
|
|
end
|
|
|
|
@spec accept(String.t()) ::
|
|
{:ok, Activity.t(), Follower.t()} | {:error, atom()} | {:error, String.t()}
|
|
def accept(address) do
|
|
Logger.debug("We're trying to accept a relay subscription")
|
|
%Actor{} = local_actor = get_actor()
|
|
|
|
with {:ok, target_instance} <- fetch_actor(address),
|
|
{: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}
|
|
else
|
|
{:error, e} ->
|
|
Logger.warning("Error while accepting remote instance follow: #{inspect(e)}")
|
|
{:error, e}
|
|
end
|
|
end
|
|
|
|
@spec reject(String.t()) ::
|
|
{:ok, Activity.t(), Follower.t()} | {:error, atom()} | {:error, String.t()}
|
|
def reject(address) do
|
|
Logger.debug("We're trying to reject a relay subscription")
|
|
%Actor{} = local_actor = get_actor()
|
|
|
|
with {:ok, target_instance} <- fetch_actor(address),
|
|
{: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}
|
|
else
|
|
{:error, e} ->
|
|
Logger.warning("Error while rejecting remote instance follow: #{inspect(e)}")
|
|
{:error, e}
|
|
end
|
|
end
|
|
|
|
@spec refresh(String.t()) ::
|
|
{:ok, Oban.Job.t()}
|
|
| {:error, Ecto.Changeset.t()}
|
|
| {:error, :bad_url}
|
|
| {:error, ActivityPubActor.make_actor_errors()}
|
|
| {:error, :no_internal_relay_actor}
|
|
| {:error, :url_nil}
|
|
def refresh(address) do
|
|
Logger.debug("We're trying to refresh a remote instance")
|
|
|
|
with {:ok, target_instance} <- fetch_actor(address),
|
|
{:ok, %Actor{id: target_actor_id}} <-
|
|
ActivityPubActor.get_or_fetch_actor_by_url(target_instance) do
|
|
Background.enqueue("refresh_profile", %{
|
|
"actor_id" => target_actor_id
|
|
})
|
|
else
|
|
{:error, e} ->
|
|
Logger.warning("Error while refreshing remote instance: #{inspect(e)}")
|
|
{:error, e}
|
|
end
|
|
end
|
|
|
|
@doc """
|
|
Publish an activity to all relays following this instance
|
|
"""
|
|
def publish(%Activity{data: %{"object" => object}} = _activity) do
|
|
with %Actor{id: actor_id} = actor <- get_actor(),
|
|
{object, object_id} <- fetch_object(object),
|
|
id <- "#{object_id}/announces/#{actor_id}" do
|
|
Logger.info("Publishing activity #{id} to all relays")
|
|
Actions.Announce.announce(actor, object, id, true, false)
|
|
else
|
|
e ->
|
|
Logger.error("Error while getting local instance actor: #{inspect(e)}")
|
|
end
|
|
end
|
|
|
|
def publish(err) do
|
|
Logger.error("Tried to publish a bad activity")
|
|
Logger.debug(inspect(err))
|
|
nil
|
|
end
|
|
|
|
defp fetch_object(object) when is_map(object) do
|
|
with {:ok, object} <- Transmogrifier.fetch_obj_helper_as_activity_streams(object) do
|
|
{object, object["id"]}
|
|
end
|
|
end
|
|
|
|
defp fetch_object(object) when is_binary(object), do: {object, object}
|
|
|
|
@spec fetch_actor(String.t()) ::
|
|
{:ok, String.t()} | {:error, WebFinger.finger_errors() | :bad_url}
|
|
# Dirty hack
|
|
defp fetch_actor("https://" <> address), do: fetch_actor(address)
|
|
defp fetch_actor("http://" <> address), do: fetch_actor(address)
|
|
|
|
defp fetch_actor(address) do
|
|
%URI{host: host} = uri = URI.parse("http://" <> address)
|
|
|
|
cond do
|
|
String.contains?(address, "@") ->
|
|
check_actor(address)
|
|
|
|
!is_nil(host) ->
|
|
uri
|
|
|> create_full_domain_string()
|
|
|> then(&Kernel.<>("relay@", &1))
|
|
|> check_actor()
|
|
|
|
true ->
|
|
{:error, :bad_url}
|
|
end
|
|
end
|
|
|
|
@spec check_actor(String.t()) :: {:ok, String.t()} | {:error, WebFinger.finger_errors()}
|
|
defp check_actor(username_and_domain) do
|
|
case Actors.get_actor_by_name(username_and_domain) do
|
|
%Actor{url: url} -> {:ok, url}
|
|
nil -> WebFinger.finger(username_and_domain)
|
|
end
|
|
end
|
|
end
|