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)
|
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\""
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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)}")
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in New Issue