formatting, following completely works
This commit is contained in:
parent
0168c366d5
commit
5b4f939721
|
@ -32,7 +32,8 @@ defmodule Vonbraun.ActivityPubReq do
|
|||
Req.get(url, headers: headers)
|
||||
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 = %{
|
||||
"host" => host,
|
||||
"accept" => "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
defmodule Vonbraun.Control do
|
||||
require Logger
|
||||
alias Vonbraun.ActivityPubReq
|
||||
alias Vonbraun.Ecto.Schema.Actor
|
||||
|
||||
defp self_id() do
|
||||
domain = Application.fetch_env!(:vonbraun, :domain)
|
||||
|
@ -8,10 +9,20 @@ defmodule Vonbraun.Control do
|
|||
"https://#{domain}/users/#{nickname}"
|
||||
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
|
||||
with {:actor, {:ok, actor}} <- {:actor, ActivityPubReq.get_actor(followee_actor_id)},
|
||||
{: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)
|
||||
# Is it a bad idea making this absolute?
|
||||
activity_id = "https://#{domain}/id/follow:#{URI.encode(followee_actor_id)}"
|
||||
|
@ -41,11 +52,32 @@ defmodule Vonbraun.Control do
|
|||
{:ok, %{:status => status, :body => body}} ->
|
||||
Logger.debug(inspect(body))
|
||||
Logger.warning("Got a status of: #{status}, probably not good.")
|
||||
{:error, {:status, status}}
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Failed to send follow request: #{inspect(error)}")
|
||||
{:error, error}
|
||||
{:error, {:post, error}}
|
||||
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
|
||||
|
||||
|
|
|
@ -169,20 +169,24 @@ defmodule Vonbraun.Ecto.Schema.Actor do
|
|||
|
||||
@spec get_my_followers() :: list(String.t())
|
||||
def get_my_followers() do
|
||||
query = from u in __MODULE__,
|
||||
where: u.follows_me_state == "accepted",
|
||||
select: u.id,
|
||||
order_by: u.follows_me_ts
|
||||
query =
|
||||
from(u in __MODULE__,
|
||||
where: u.follows_me_state == "accepted",
|
||||
select: u.id,
|
||||
order_by: u.follows_me_ts
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
@spec get_my_follows() :: list(String.t())
|
||||
def get_my_follows() do
|
||||
query = from u in __MODULE__,
|
||||
where: u.following_state == "accepted",
|
||||
select: u.id,
|
||||
order_by: u.following_ts
|
||||
query =
|
||||
from(u in __MODULE__,
|
||||
where: u.following_state == "accepted",
|
||||
select: u.id,
|
||||
order_by: u.following_ts
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
|
|
@ -53,21 +53,27 @@ defmodule Vonbraun.Fluree do
|
|||
i_follow? = Keyword.get(options, :following, false)
|
||||
|
||||
props = Map.new()
|
||||
props = if me? do
|
||||
Map.put(props, "me", true)
|
||||
else
|
||||
props
|
||||
end
|
||||
props = if is_follower? do
|
||||
Map.put(props, "isFollower", true)
|
||||
else
|
||||
props
|
||||
end
|
||||
props = if i_follow? do
|
||||
Map.put(props, "following", true)
|
||||
else
|
||||
props
|
||||
end
|
||||
|
||||
props =
|
||||
if me? do
|
||||
Map.put(props, "me", true)
|
||||
else
|
||||
props
|
||||
end
|
||||
|
||||
props =
|
||||
if is_follower? do
|
||||
Map.put(props, "isFollower", true)
|
||||
else
|
||||
props
|
||||
end
|
||||
|
||||
props =
|
||||
if i_follow? do
|
||||
Map.put(props, "following", true)
|
||||
else
|
||||
props
|
||||
end
|
||||
|
||||
Map.put(actor, "vonbraun:props", props)
|
||||
|
||||
|
|
|
@ -62,65 +62,70 @@ defmodule Vonbraun.HTTPSignature do
|
|||
valid_request_target = "post #{target}"
|
||||
|
||||
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")},
|
||||
{:parse,
|
||||
{:ok, %{
|
||||
"keyId" => _key_id,
|
||||
"headers" => signing_headers,
|
||||
"signature" => {:ok, signature}
|
||||
}
|
||||
}} <- {:parse, parse_signature_header(signature_header)} do
|
||||
# Reconstruct the signing data
|
||||
reconstructed_header_list = Enum.reduce_while(signing_headers |> Enum.reverse(), [], fn
|
||||
"(request-target)", list ->
|
||||
{:cont, ["(request-target): #{valid_request_target}" | list]}
|
||||
{:ok,
|
||||
%{
|
||||
"keyId" => _key_id,
|
||||
"headers" => signing_headers,
|
||||
"signature" => {:ok, signature}
|
||||
}}} <- {:parse, parse_signature_header(signature_header)} do
|
||||
# Reconstruct the signing data
|
||||
reconstructed_header_list =
|
||||
Enum.reduce_while(signing_headers |> Enum.reverse(), [], fn
|
||||
"(request-target)", list ->
|
||||
{:cont, ["(request-target): #{valid_request_target}" | list]}
|
||||
|
||||
"digest", list ->
|
||||
with {:get, [header_value]} <- {:get, Conn.get_req_header(conn, "digest")},
|
||||
{:split, [digest_algo, encoded_digest]} <- {:split, String.split(header_value, "=", parts: 2)},
|
||||
{:decode, {:ok, provided_digest}} <- {:decode, Base.decode64(encoded_digest)} do
|
||||
new_digest = :crypto.hash(:sha256, raw_body)
|
||||
test_digest = digest_algo <> "=" <> (new_digest |> Base.encode64())
|
||||
"digest", list ->
|
||||
with {:get, [header_value]} <- {:get, Conn.get_req_header(conn, "digest")},
|
||||
{:split, [digest_algo, encoded_digest]} <-
|
||||
{:split, String.split(header_value, "=", parts: 2)},
|
||||
{:decode, {:ok, provided_digest}} <- {:decode, Base.decode64(encoded_digest)} do
|
||||
new_digest = :crypto.hash(:sha256, raw_body)
|
||||
test_digest = digest_algo <> "=" <> (new_digest |> Base.encode64())
|
||||
|
||||
if provided_digest == new_digest do
|
||||
{:cont, ["digest: #{test_digest}" | list]}
|
||||
else
|
||||
Logger.warning("digest failed: #{provided_digest} <> #{test_digest}")
|
||||
{: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
|
||||
if provided_digest == new_digest do
|
||||
{:cont, ["digest: #{test_digest}" | list]}
|
||||
else
|
||||
Logger.warning("digest failed: #{provided_digest} <> #{test_digest}")
|
||||
{: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("Verify http sig unmatch: #{inspect(error)}")
|
||||
|
|
|
@ -44,13 +44,16 @@ defmodule Vonbraun.InboxRouter do
|
|||
|
||||
:rejected ->
|
||||
200
|
||||
|
||||
_ ->
|
||||
200
|
||||
end
|
||||
|
||||
send_resp(conn, status_code, "boop")
|
||||
else
|
||||
error ->
|
||||
{:error, error} ->
|
||||
Logger.warning("Some kind of failure: #{inspect(error)}")
|
||||
send_resp(conn, 401, "fuck off")
|
||||
send_resp(conn, 500, "I fucked up")
|
||||
end
|
||||
|
||||
true ->
|
||||
|
@ -58,10 +61,34 @@ defmodule Vonbraun.InboxRouter do
|
|||
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(
|
||||
%{"type" => "Follow", "actor" => actor_id, "object" => follow_target},
|
||||
actor = %{}
|
||||
) do
|
||||
)
|
||||
when is_binary(follow_target) do
|
||||
with {:valid_target, true} <- {:valid_target, ActivityPubReq.actor_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(actor_id)} do
|
||||
|
@ -103,10 +130,12 @@ defmodule Vonbraun.InboxRouter do
|
|||
case ActivityPubReq.post(inbox, payload) do
|
||||
{:ok, %{:status => status, :body => body}} ->
|
||||
Logger.debug("Accept response status: #{status} body: #{inspect(body)}")
|
||||
|
||||
{:error, error} ->
|
||||
Logger.error("Failed to Accept: #{inspect(error)}")
|
||||
end
|
||||
end)
|
||||
|
||||
{:ok, String.to_atom(follows_me_state)}
|
||||
else
|
||||
{:inbox, {:error, _}} ->
|
||||
|
@ -131,7 +160,45 @@ defmodule Vonbraun.InboxRouter do
|
|||
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}
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue