From 49d2ac5648f959f3009e018e9e0e1c0eafc7b1d2 Mon Sep 17 00:00:00 2001 From: Moon Man Date: Fri, 6 Sep 2024 18:24:12 +0000 Subject: [PATCH] lots of changes, untested --- lib/vonbraun/activity_pub/handler.ex | 28 ++++++++++--- lib/vonbraun/activity_pub/handler/accept.ex | 46 ++++++++++++++------- lib/vonbraun/activity_pub/handler/delete.ex | 2 +- lib/vonbraun/activity_pub/handler/follow.ex | 24 +++++++---- lib/vonbraun/activity_pub/handler/reject.ex | 26 ++++++++---- lib/vonbraun/activity_pub/handler/undo.ex | 15 ++++--- lib/vonbraun/util.ex | 2 +- 7 files changed, 97 insertions(+), 46 deletions(-) diff --git a/lib/vonbraun/activity_pub/handler.ex b/lib/vonbraun/activity_pub/handler.ex index a41fdf8..5f2162e 100644 --- a/lib/vonbraun/activity_pub/handler.ex +++ b/lib/vonbraun/activity_pub/handler.ex @@ -19,11 +19,29 @@ defmodule Vonbraun.ActivityPub.Handler do end) end - @spec handle(%{type: String.t()}, map()) :: :ok | {:ok, atom()} | {:error, any()} - def handle(activity = %{"type" => type}, actor = %{}) when is_binary(type) do - Agent.get(__MODULE__, fn map -> - func = Map.get(map, type, fn _, _ -> {:ok, :type} end) - apply(func, [activity, actor]) + @doc """ + Passed a list of types, gets the first handler that applies to a type. + """ + def get_first_matching_handler(types) when is_list(types) do + Agent.get(__MODULE__, fn handler_map -> + Enum.reduce_while(types, nil, fn + type, _ -> + case Map.get(handler_map, type) do + nil -> {:cont, nil} + func -> {:halt, func} + end + end) end) end + + @spec handle(%{type: String.t()}, map()) :: {:ok, atom()} | {:error, any()} + def handle(activity = %{"@type" => types}, actor = %{}) when is_list(types) do + case get_first_matching_handler(types) do + nil -> + {:ok, :ignore} + + func -> + apply(func, [activity, actor]) + end + end end diff --git a/lib/vonbraun/activity_pub/handler/accept.ex b/lib/vonbraun/activity_pub/handler/accept.ex index bbf23d3..7b6a975 100644 --- a/lib/vonbraun/activity_pub/handler/accept.ex +++ b/lib/vonbraun/activity_pub/handler/accept.ex @@ -5,43 +5,54 @@ defmodule Vonbraun.ActivityPub.Handler.Accept do alias Vonbraun.Util alias Vonbraun.Ecto.Schema.Actor - @verb "Accept" + @verb "https://www.w3.org/ns/activitystreams#Accept" + @follow "https://www.w3.org/ns/activitystreams#Follow" def type, do: @verb - def extract_follow_object_actor(%{"type" => "Follow", "actor" => actor_id}) - when is_binary(actor_id) do - {:ok, actor_id} + defp is_follow_object?(%{"@type" => types}) when is_list(types), do: @follow in types + + # The actor is specified here so we can just use it. + @spec extract_follow_object_actor_id(map()) :: :error | {:error, :not_found} | {:ok, binary()} + def extract_follow_object_actor_id(object = %{"actor" => actor_value}) do + if is_follow_object?(object) do + Util.get_only_id(actor_value) + else + {:error, :not_found} + end end - def extract_follow_object_actor(%{"type" => "Follow", "id" => provided_follow_activity_id}) - when is_binary(provided_follow_activity_id) do + # Only the ID was provided, so extract actor from the ID. + def extract_follow_object_actor_id(object = %{"@id" => _}) do domain = Application.fetch_env!(:vonbraun, :domain) actual_activity_id_prefix = "https://#{domain}/id/follow:" - with ^actual_activity_id_prefix <> actor_id <- provided_follow_activity_id do - {:ok, actor_id} + with {:ok, provided_follow_activity_id} <- Util.get_only_id(object), + ^actual_activity_id_prefix <> raw_actor_id <- provided_follow_activity_id do + {:ok, URI.decode(raw_actor_id)} else _ -> {:error, :not_found} end end - def extract_follow_object_actor(_) do + def extract_follow_object_actor_id(_) do {:error, :not_found} end # Lots of kinds of things can be accepted but for right now only follows. def handle( %{ - "type" => @verb, - "actor" => actor_id, - "object" => object = %{"type" => "Follow"} + "actor" => actor_value, + "object" => object_value }, %{} ) do - with {:actor, {:ok, follow_actor_id}} <- {:actor, extract_follow_object_actor(object)}, - {:match, true} <- {:match, follow_actor_id == Util.my_id()}, + my_id = Util.my_id() + + with {:object, {:ok, object}} <- {:object, Util.get_only_property(object_value)}, + {:actor, {:ok, ^my_id}} <- {:actor, extract_follow_object_actor_id(object)}, + {:actor_id, {:ok, actor_id}} <- {:actor_id, Util.get_only_id(actor_value)}, {:asked, {:ok, %Actor{:blocked => nil, :following_state => "accepted"}}} <- {:asked, Actor.mark_pending_follow(actor_id, "accepted", force: true)} do Logger.info("Now following: #{actor_id}") @@ -55,7 +66,7 @@ defmodule Vonbraun.ActivityPub.Handler.Accept do {:asked, {:ok, %Actor{:blocked => nil, :following_state => following_state}}} -> Logger.error( - "Weird following state after received Accept: #{following_state} from actor: #{actor_id} this should not happen." + "Weird following state after received Accept: #{following_state} from actor: #{inspect(actor_value)} this should not happen." ) {:error, :following_state} @@ -63,7 +74,10 @@ defmodule Vonbraun.ActivityPub.Handler.Accept do {:actor, {:error, _error}} -> {:ok, :unauthorized} - {:match, false} -> + {:actor_id, :error} -> + {:ok, :unauthorized} + + {:object, :error} -> {:ok, :unauthorized} end end diff --git a/lib/vonbraun/activity_pub/handler/delete.ex b/lib/vonbraun/activity_pub/handler/delete.ex index 2cacdcb..d9ff44e 100644 --- a/lib/vonbraun/activity_pub/handler/delete.ex +++ b/lib/vonbraun/activity_pub/handler/delete.ex @@ -1,7 +1,7 @@ defmodule Vonbraun.ActivityPub.Handler.Delete do @behaviour Vonbraun.ActivityPub.HandlerBehaviour - @verb "Delete" + @verb "https://www.w3.org/ns/activitystreams#Delete" def type, do: @verb diff --git a/lib/vonbraun/activity_pub/handler/follow.ex b/lib/vonbraun/activity_pub/handler/follow.ex index fc4ad99..6be646e 100644 --- a/lib/vonbraun/activity_pub/handler/follow.ex +++ b/lib/vonbraun/activity_pub/handler/follow.ex @@ -7,21 +7,23 @@ defmodule Vonbraun.ActivityPub.Handler.Follow do alias Vonbraun.ActivityPub.Object alias Vonbraun.Util - @verb "Follow" + @verb "https://www.w3.org/ns/activitystreams#Follow" def type, do: @verb def handle( - %{ - "id" => activity_id, - "type" => @verb, - "actor" => follow_requester_id, + activity = %{ + "actor" => actor_value, "object" => follow_target }, actor = %{} ) - when is_binary(follow_requester_id) and is_binary(follow_target) and is_binary(activity_id) do - with {:valid_target, true} <- {:valid_target, Util.my_id() == follow_target}, + when is_binary(follow_target) do + my_id = Util.my_id() + + with {:activity_id, {:ok, activity_id}} <- {:activity_id, Util.get_only_id(activity)}, + {:actor_id, {:ok, follow_requester_id}} <- {:actor_id, Util.get_only_id(actor_value)}, + {:valid_target, {:ok, ^my_id}} <- {:valid_target, Util.get_only_id(follow_target)}, {:add, {:ok, %Actor{:blocked => nil, :follows_me_state => follows_me_state}}} when not is_nil(follows_me_state) <- {:add, Actor.maybe_add_follower(follow_requester_id)} do @@ -66,7 +68,13 @@ defmodule Vonbraun.ActivityPub.Handler.Follow do {:ok, :ignored} end else - {:valid_target, false} -> + {:actor_id, :error} -> + {:ok, :unauthorized} + + {:activity_id, :error} -> + {:ok, :unauthorized} + + {:valid_target, _} -> {:ok, :unauthorized} {:add, {:ok, %Actor{:blocked => blocked_ts}}} when not is_nil(blocked_ts) -> diff --git a/lib/vonbraun/activity_pub/handler/reject.ex b/lib/vonbraun/activity_pub/handler/reject.ex index c236c41..4a84b53 100644 --- a/lib/vonbraun/activity_pub/handler/reject.ex +++ b/lib/vonbraun/activity_pub/handler/reject.ex @@ -4,19 +4,24 @@ defmodule Vonbraun.ActivityPub.Handler.Reject do require Logger alias Vonbraun.Ecto.Schema.Actor alias Vonbraun.Util - import Vonbraun.ActivityPub.Handler.Accept, only: [extract_follow_object_actor: 1] - @verb "Reject" + import Vonbraun.ActivityPub.Handler.Accept, + only: [extract_follow_object_actor_id: 1] + + @verb "https://www.w3.org/ns/activitystreams#Reject" + def type, do: @verb # Lots of kinds of things can be rejected but for right now only follows. def handle(%{ - "type" => @verb, - "actor" => actor_id, - "object" => object = %{"type" => "Follow"} + "actor" => actor_value, + "object" => object_value }) do - with {:actor, {:ok, follow_actor_id}} <- {:actor, extract_follow_object_actor(object)}, - {:match, true} <- {:match, follow_actor_id == Util.my_id()}, + my_id = Util.my_id() + + with {:object, {:ok, object}} <- {:object, Util.get_only_property(object_value)}, + {:actor, {:ok, ^my_id}} <- {:actor, extract_follow_object_actor_id(object)}, + {:actor_id, {:ok, actor_id}} <- {:actor_id, Util.get_only_id(actor_value)}, {:asked, {:ok, %Actor{:blocked => nil, :following_state => "accepted"}}} <- {:asked, Actor.mark_pending_follow(actor_id, "rejected", force: true)} do Logger.info("Now following: #{actor_id}") @@ -30,7 +35,7 @@ defmodule Vonbraun.ActivityPub.Handler.Reject do {:asked, {:ok, %Actor{:blocked => nil, :following_state => following_state}}} -> Logger.error( - "Weird following state after received Accept: #{following_state} from actor: #{actor_id} this should not happen." + "Weird following state after received Accept: #{following_state} from actor: #{inspect(actor_value)} this should not happen." ) {:error, :following_state} @@ -38,7 +43,10 @@ defmodule Vonbraun.ActivityPub.Handler.Reject do {:actor, {:error, _error}} -> {:ok, :unauthorized} - {:match, false} -> + {:actor_id, :error} -> + {:ok, :unauthorized} + + {:object, :error} -> {:ok, :unauthorized} end end diff --git a/lib/vonbraun/activity_pub/handler/undo.ex b/lib/vonbraun/activity_pub/handler/undo.ex index a56f2e4..c913b29 100644 --- a/lib/vonbraun/activity_pub/handler/undo.ex +++ b/lib/vonbraun/activity_pub/handler/undo.ex @@ -4,17 +4,20 @@ defmodule Vonbraun.ActivityPub.Handler.Undo do require Logger alias Vonbraun.Ecto.Schema.Actor alias Vonbraun.Util - import Vonbraun.ActivityPub.Handler.Accept, only: [extract_follow_object_actor: 1] + import Vonbraun.ActivityPub.Handler.Accept, only: [extract_follow_object_actor_id: 1] - @verb "Undo" + @verb "https://www.w3.org/ns/activitystreams#Undo" def type, do: @verb # Lots of different kinds of things can be undone. - def handle(%{"type" => @verb, "actor" => actor_id, "object" => object = %{"type" => "Follow"}}) do - with {:actor, {:ok, follow_actor_id}} <- {:actor, extract_follow_object_actor(object)}, - {:match, true} <- {:match, follow_actor_id == Util.my_id()}, + def handle(%{"actor" => actor_value, "object" => object_value}) do + my_id = Util.my_id() + + with {:object, {:ok, object}} <- {:object, Util.get_only_property(object_value)}, + {:actor, {:ok, ^my_id}} <- {:actor, extract_follow_object_actor_id(object)}, + {:actor_id, {:ok, actor_id}} <- {:actor_id, Util.get_only_id(actor_value)}, {:asked, {:ok, %Actor{:following_state => nil}}} <- {:asked, Actor.remove_follower(actor_id)} do {:ok, :removed_follower} @@ -30,7 +33,7 @@ defmodule Vonbraun.ActivityPub.Handler.Undo do end end - def handle(%{"type" => @verb}, %{}) do + def handle(%{}, %{}) do {:ok, :unknown} end end diff --git a/lib/vonbraun/util.ex b/lib/vonbraun/util.ex index 5763b41..6d79e7b 100644 --- a/lib/vonbraun/util.ex +++ b/lib/vonbraun/util.ex @@ -30,7 +30,7 @@ defmodule Vonbraun.Util do """ def get_only_property([property]), do: {:ok, property} - def get_only_property(property) when is_list(property), do: {:error} + def get_only_property(property) when is_list(property), do: :error def get_only_property(property), do: {:ok, property}