formatting, following completely works

This commit is contained in:
Moon Man 2024-08-27 01:37:10 +00:00
parent 0168c366d5
commit 5b4f939721
6 changed files with 198 additions and 83 deletions

View File

@ -32,7 +32,8 @@ defmodule Vonbraun.ActivityPubReq do
Req.get(url, headers: headers) Req.get(url, headers: headers)
end end
def post(url = %URI{:path => path, :query => query, :host => host}, body) when is_binary(body) do def post(url = %URI{:path => path, :query => query, :host => host}, body)
when is_binary(body) do
headers = %{ headers = %{
"host" => host, "host" => host,
"accept" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"" "accept" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""

View File

@ -1,6 +1,7 @@
defmodule Vonbraun.Control do defmodule Vonbraun.Control do
require Logger require Logger
alias Vonbraun.ActivityPubReq alias Vonbraun.ActivityPubReq
alias Vonbraun.Ecto.Schema.Actor
defp self_id() do defp self_id() do
domain = Application.fetch_env!(:vonbraun, :domain) domain = Application.fetch_env!(:vonbraun, :domain)
@ -8,10 +9,20 @@ defmodule Vonbraun.Control do
"https://#{domain}/users/#{nickname}" "https://#{domain}/users/#{nickname}"
end end
@spec follow(String.t()) ::
:ok
| {:error,
:blocked
| :invalid_inbox
| {:actor, {any(), any()} | map()}
| {:post, map()}
| {:status, non_neg_integer()}}
def follow(followee_actor_id) when is_binary(followee_actor_id) do def follow(followee_actor_id) when is_binary(followee_actor_id) do
with {:actor, {:ok, actor}} <- {:actor, ActivityPubReq.get_actor(followee_actor_id)}, with {:actor, {:ok, actor}} <- {:actor, ActivityPubReq.get_actor(followee_actor_id)},
{:inbox, {:ok, raw_inbox}} <- {:inbox, ActivityPubReq.extract_actor_inbox(actor)}, {:inbox, {:ok, raw_inbox}} <- {:inbox, ActivityPubReq.extract_actor_inbox(actor)},
{:parse_inbox, inbox = %URI{}} <- {:parse_inbox, URI.parse(raw_inbox)} do {:parse_inbox, inbox = %URI{}} <- {:parse_inbox, URI.parse(raw_inbox)},
{:pending, {:ok, %{:blocks_me => nil, :following_state => "pending"}}} <-
{:pending, Actor.mark_pending_follow(followee_actor_id, "pending")} do
domain = Application.fetch_env!(:vonbraun, :domain) domain = Application.fetch_env!(:vonbraun, :domain)
# Is it a bad idea making this absolute? # Is it a bad idea making this absolute?
activity_id = "https://#{domain}/id/follow:#{URI.encode(followee_actor_id)}" activity_id = "https://#{domain}/id/follow:#{URI.encode(followee_actor_id)}"
@ -41,11 +52,32 @@ defmodule Vonbraun.Control do
{:ok, %{:status => status, :body => body}} -> {:ok, %{:status => status, :body => body}} ->
Logger.debug(inspect(body)) Logger.debug(inspect(body))
Logger.warning("Got a status of: #{status}, probably not good.") Logger.warning("Got a status of: #{status}, probably not good.")
{:error, {:status, status}}
{:error, error} -> {:error, error} ->
Logger.error("Failed to send follow request: #{inspect(error)}") Logger.error("Failed to send follow request: #{inspect(error)}")
{:error, error} {:error, {:post, error}}
end end
else
{:pending, {:ok, %{:blocks_me => nil, :following_state => "accepted"}}} ->
Logger.info("I think you already follow them.")
:ok
{:pending, {:ok, %{:blocks_me => nil, :following_state => "rejected"}}} ->
Logger.warning("I think they already rejected your follow")
:ok
{:actor, {:error, error}} ->
Logger.warning("Failed to query remote actor: #{inspect(error)}")
{:error, {:actor, error}}
{:pending, {:ok, %{:blocks_me => _}}} ->
Logger.info("This user blocks you.")
{:error, :blocked}
{:inbox, {:error, _}} ->
Logger.warning("Actor had an invalid inbox.")
{:error, :invalid_inbox}
end end
end end

View File

@ -169,20 +169,24 @@ defmodule Vonbraun.Ecto.Schema.Actor do
@spec get_my_followers() :: list(String.t()) @spec get_my_followers() :: list(String.t())
def get_my_followers() do def get_my_followers() do
query = from u in __MODULE__, query =
where: u.follows_me_state == "accepted", from(u in __MODULE__,
select: u.id, where: u.follows_me_state == "accepted",
order_by: u.follows_me_ts select: u.id,
order_by: u.follows_me_ts
)
Repo.all(query) Repo.all(query)
end end
@spec get_my_follows() :: list(String.t()) @spec get_my_follows() :: list(String.t())
def get_my_follows() do def get_my_follows() do
query = from u in __MODULE__, query =
where: u.following_state == "accepted", from(u in __MODULE__,
select: u.id, where: u.following_state == "accepted",
order_by: u.following_ts select: u.id,
order_by: u.following_ts
)
Repo.all(query) Repo.all(query)
end end

View File

@ -53,21 +53,27 @@ defmodule Vonbraun.Fluree do
i_follow? = Keyword.get(options, :following, false) i_follow? = Keyword.get(options, :following, false)
props = Map.new() props = Map.new()
props = if me? do
Map.put(props, "me", true) props =
else if me? do
props Map.put(props, "me", true)
end else
props = if is_follower? do props
Map.put(props, "isFollower", true) end
else
props props =
end if is_follower? do
props = if i_follow? do Map.put(props, "isFollower", true)
Map.put(props, "following", true) else
else props
props end
end
props =
if i_follow? do
Map.put(props, "following", true)
else
props
end
Map.put(actor, "vonbraun:props", props) Map.put(actor, "vonbraun:props", props)

View File

@ -62,65 +62,70 @@ defmodule Vonbraun.HTTPSignature do
valid_request_target = "post #{target}" valid_request_target = "post #{target}"
with {:raw_body, {:ok, raw_body, _}} <- {:raw_body, Conn.read_body(conn)}, with {:raw_body, {:ok, raw_body, _}} <- {:raw_body, Conn.read_body(conn)},
{:signature_header, [signature_header]} <- {:signature_header, [signature_header]} <-
{:signature_header, Conn.get_req_header(conn, "signature")}, {:signature_header, Conn.get_req_header(conn, "signature")},
{:parse, {:parse,
{:ok, %{ {:ok,
"keyId" => _key_id, %{
"headers" => signing_headers, "keyId" => _key_id,
"signature" => {:ok, signature} "headers" => signing_headers,
} "signature" => {:ok, signature}
}} <- {:parse, parse_signature_header(signature_header)} do }}} <- {:parse, parse_signature_header(signature_header)} do
# Reconstruct the signing data # Reconstruct the signing data
reconstructed_header_list = Enum.reduce_while(signing_headers |> Enum.reverse(), [], fn reconstructed_header_list =
"(request-target)", list -> Enum.reduce_while(signing_headers |> Enum.reverse(), [], fn
{:cont, ["(request-target): #{valid_request_target}" | list]} "(request-target)", list ->
{:cont, ["(request-target): #{valid_request_target}" | list]}
"digest", list -> "digest", list ->
with {:get, [header_value]} <- {:get, Conn.get_req_header(conn, "digest")}, with {:get, [header_value]} <- {:get, Conn.get_req_header(conn, "digest")},
{:split, [digest_algo, encoded_digest]} <- {:split, String.split(header_value, "=", parts: 2)}, {:split, [digest_algo, encoded_digest]} <-
{:decode, {:ok, provided_digest}} <- {:decode, Base.decode64(encoded_digest)} do {:split, String.split(header_value, "=", parts: 2)},
new_digest = :crypto.hash(:sha256, raw_body) {:decode, {:ok, provided_digest}} <- {:decode, Base.decode64(encoded_digest)} do
test_digest = digest_algo <> "=" <> (new_digest |> Base.encode64()) new_digest = :crypto.hash(:sha256, raw_body)
test_digest = digest_algo <> "=" <> (new_digest |> Base.encode64())
if provided_digest == new_digest do if provided_digest == new_digest do
{:cont, ["digest: #{test_digest}" | list]} {:cont, ["digest: #{test_digest}" | list]}
else else
Logger.warning("digest failed: #{provided_digest} <> #{test_digest}") Logger.warning("digest failed: #{provided_digest} <> #{test_digest}")
{:halt, false} {:halt, false}
end
else
error ->
Logger.warning("Failed to handle digest: #{inspect(error)}")
{:halt, false}
end
header_name, list ->
case Conn.get_req_header(conn, header_name) do
[] ->
Logger.warning("header not found for http signature verification: #{header_name}")
{:halt, false}
[header_value] ->
{:cont, ["#{header_name}: #{header_value}" | list]}
end
end)
case reconstructed_header_list do
false -> false
_ ->
# Hope they use the same newline chars
test_str = Enum.join(reconstructed_header_list, "\n")
Logger.debug("Here is the test string for signature verification: `#{test_str}`")
case ExPublicKey.verify(test_str, signature, public_key) do
{:ok, val} -> val
error ->
Logger.warning("Error verifying signature: #{inspect(error)}")
false
end end
else
error ->
Logger.warning("Failed to handle digest: #{inspect(error)}")
{:halt, false}
end
header_name, list ->
case Conn.get_req_header(conn, header_name) do
[] ->
Logger.warning("header not found for http signature verification: #{header_name}")
{:halt, false}
[header_value] ->
{:cont, ["#{header_name}: #{header_value}" | list]}
end
end)
case reconstructed_header_list do
false ->
false
_ ->
# Hope they use the same newline chars
test_str = Enum.join(reconstructed_header_list, "\n")
Logger.debug("Here is the test string for signature verification: `#{test_str}`")
case ExPublicKey.verify(test_str, signature, public_key) do
{:ok, val} ->
val
error ->
Logger.warning("Error verifying signature: #{inspect(error)}")
false
end end
end
else else
error -> error ->
Logger.warning("Verify http sig unmatch: #{inspect(error)}") Logger.warning("Verify http sig unmatch: #{inspect(error)}")

View File

@ -44,13 +44,16 @@ defmodule Vonbraun.InboxRouter do
:rejected -> :rejected ->
200 200
_ ->
200
end end
send_resp(conn, status_code, "boop") send_resp(conn, status_code, "boop")
else else
error -> {:error, error} ->
Logger.warning("Some kind of failure: #{inspect(error)}") Logger.warning("Some kind of failure: #{inspect(error)}")
send_resp(conn, 401, "fuck off") send_resp(conn, 500, "I fucked up")
end end
true -> true ->
@ -58,10 +61,34 @@ defmodule Vonbraun.InboxRouter do
end end
end end
defp extract_follow_object_actor(%{"type" => "Follow", "actor" => actor_id})
when is_binary(actor_id) do
{:ok, actor_id}
end
defp extract_follow_object_actor(%{"type" => "Follow", "id" => provided_follow_activity_id})
when is_binary(provided_follow_activity_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}
else
_ ->
{:error, :notfound}
end
end
defp extract_follow_object_actor(_) do
{:error, :notfound}
end
# TODO: make sure I'm in the to list.
defp handle_activity( defp handle_activity(
%{"type" => "Follow", "actor" => actor_id, "object" => follow_target}, %{"type" => "Follow", "actor" => actor_id, "object" => follow_target},
actor = %{} actor = %{}
) do )
when is_binary(follow_target) do
with {:valid_target, true} <- {:valid_target, ActivityPubReq.actor_id() == follow_target}, with {:valid_target, true} <- {:valid_target, ActivityPubReq.actor_id() == follow_target},
{:add, {:ok, %Actor{:blocked => nil, :follows_me_state => follows_me_state}}} {:add, {:ok, %Actor{:blocked => nil, :follows_me_state => follows_me_state}}}
when not is_nil(follows_me_state) <- {:add, Actor.maybe_add_follower(actor_id)} do when not is_nil(follows_me_state) <- {:add, Actor.maybe_add_follower(actor_id)} do
@ -103,10 +130,12 @@ defmodule Vonbraun.InboxRouter do
case ActivityPubReq.post(inbox, payload) do case ActivityPubReq.post(inbox, payload) do
{:ok, %{:status => status, :body => body}} -> {:ok, %{:status => status, :body => body}} ->
Logger.debug("Accept response status: #{status} body: #{inspect(body)}") Logger.debug("Accept response status: #{status} body: #{inspect(body)}")
{:error, error} -> {:error, error} ->
Logger.error("Failed to Accept: #{inspect(error)}") Logger.error("Failed to Accept: #{inspect(error)}")
end end
end) end)
{:ok, String.to_atom(follows_me_state)} {:ok, String.to_atom(follows_me_state)}
else else
{:inbox, {:error, _}} -> {:inbox, {:error, _}} ->
@ -131,7 +160,45 @@ defmodule Vonbraun.InboxRouter do
end end
end end
defp handle_activity(_activity = %{}, _) do defp handle_activity(
%{
"type" => "Accept",
"actor" => actor_id,
"object" => object = %{"type" => "Follow"}
},
_actor = %{}
)
when is_binary(actor_id) do
with {:asked, {:ok, %Actor{:blocked => nil, :following_state => "accepted"}}} <-
{:asked, Actor.mark_pending_follow(actor_id, "accepted", force: true)},
{:actor, {:ok, follow_actor_id}} <- {:actor, extract_follow_object_actor(object)},
{:match, true} <- {:match, follow_actor_id == ActivityPubReq.actor_id()} do
Logger.info("Now following: #{actor_id}")
{:ok, :following}
else
{:asked, {:error, :blocked}} ->
{:ok, :blocked_user}
{:asked, {:error, error}} ->
{:error, error}
{: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."
)
{:error, :following_state}
{:actor, {:error, _error}} ->
{:ok, :unauthorized}
{:match, false} ->
{:ok, :unauthorized}
end
end
defp handle_activity(activity = %{}, _) do
Logger.warning("I don't know what to do with it: #{inspect(activity)}")
{:ok, :ignored} {:ok, :ignored}
end end
end end