Improve group refreshment and fixed date signature generation

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2021-11-13 18:45:01 +01:00
parent 6d599441a9
commit 55af776df9
27 changed files with 1009 additions and 704 deletions

View File

@@ -32,14 +32,14 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
case Actors.get_actor_by_url(url, preload) do
{:ok, %Actor{} = cached_actor} ->
if Actors.needs_update?(cached_actor) do
__MODULE__.make_actor_from_url(url, preload)
__MODULE__.make_actor_from_url(url, preload: preload)
else
{:ok, cached_actor}
end
{:error, :actor_not_found} ->
# For tests, see https://github.com/jjh42/mock#not-supported---mocking-internal-function-calls and Mobilizon.Federation.ActivityPubTest
__MODULE__.make_actor_from_url(url, preload)
__MODULE__.make_actor_from_url(url, preload: preload)
end
end
@@ -48,15 +48,15 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
@doc """
Create an actor locally by its URL (AP ID)
"""
@spec make_actor_from_url(url :: String.t(), preload :: boolean()) ::
@spec make_actor_from_url(url :: String.t(), options :: Keyword.t()) ::
{:ok, Actor.t()} | {:error, make_actor_errors | Ecto.Changeset.t()}
def make_actor_from_url(url, preload \\ false) do
def make_actor_from_url(url, options \\ []) do
if are_same_origin?(url, Endpoint.url()) do
{:error, :actor_is_local}
else
case Fetcher.fetch_and_prepare_actor_from_url(url) do
case Fetcher.fetch_and_prepare_actor_from_url(url, options) do
{:ok, data} when is_map(data) ->
Actors.upsert_actor(data, preload)
Actors.upsert_actor(data, Keyword.get(options, :preload, false))
# Request returned 410
{:error, :actor_deleted} ->
@@ -78,13 +78,13 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
case Actors.get_actor_by_name_with_preload(nickname, type) do
%Actor{url: actor_url} = actor ->
if Actors.needs_update?(actor) do
make_actor_from_url(actor_url, true)
make_actor_from_url(actor_url, preload: true)
else
{:ok, actor}
end
nil ->
make_actor_from_nickname(nickname, true)
make_actor_from_nickname(nickname, preload: true)
end
end
@@ -100,7 +100,7 @@ defmodule Mobilizon.Federation.ActivityPub.Actor do
def make_actor_from_nickname(nickname, preload \\ false) do
case WebFinger.finger(nickname) do
{:ok, url} when is_binary(url) ->
make_actor_from_url(url, preload)
make_actor_from_url(url, preload: preload)
{:error, e} ->
{:error, e}

View File

@@ -49,8 +49,11 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
{:error, :content_not_json}
{:ok, %Tesla.Env{} = res} ->
Logger.debug("Resource returned bad HTTP code inspect #{res}")
Logger.debug("Resource returned bad HTTP code #{inspect(res)}")
{:error, :http_error}
{:error, err} ->
{:error, err}
end
else
{:error, :invalid_url}
@@ -122,39 +125,25 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
"""
@spec fetch_and_prepare_actor_from_url(String.t()) ::
{:ok, map()} | {:error, fetch_actor_errors}
def fetch_and_prepare_actor_from_url(url) do
def fetch_and_prepare_actor_from_url(url, options \\ []) do
Logger.debug("Fetching and preparing actor from url")
Logger.debug(inspect(url))
case Tesla.get(url,
headers: [{"Accept", "application/activity+json"}],
follow_redirect: true
) do
{:ok, %{status: 200, body: body}} ->
Logger.debug("response okay, now decoding json")
case fetch(url, options) do
{:ok, data} ->
case ActorConverter.as_to_model_data(data) do
{:error, :actor_not_allowed_type} ->
{:error, :actor_not_allowed_type}
case Jason.decode(body) do
{:ok, data} when is_map(data) ->
Logger.debug("Got activity+json response at actor's endpoint, now converting data")
case ActorConverter.as_to_model_data(data) do
{:error, :actor_not_allowed_type} ->
{:error, :actor_not_allowed_type}
map when is_map(map) ->
{:ok, map}
end
{:error, %Jason.DecodeError{} = e} ->
Logger.warn("Could not decode actor at fetch #{url}, #{inspect(e)}")
{:error, :json_decode_error}
map when is_map(map) ->
{:ok, map}
end
{:ok, %{status: 410}} ->
{:error, :http_gone} ->
Logger.info("Response HTTP 410")
{:error, :actor_deleted}
{:ok, %Tesla.Env{}} ->
{:error, :http_error} ->
Logger.info("Non 200 HTTP Code")
{:error, :http_error}

View File

@@ -52,7 +52,7 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
@spec fetch_group(String.t(), Actor.t()) :: :ok | {:error, fetch_actor_errors}
def fetch_group(group_url, %Actor{} = on_behalf_of) do
case ActivityPubActor.make_actor_from_url(group_url) do
case ActivityPubActor.make_actor_from_url(group_url, on_behalf_of: on_behalf_of) do
{:error, err}
when err in [:actor_deleted, :http_error, :json_decode_error, :actor_is_local] ->
Logger.debug("Error while making actor")

View File

@@ -114,8 +114,12 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
Logger.debug("headers")
Logger.debug(inspect(headers))
with {:ok, key} <- prepare_public_key(keys) do
HTTPSignatures.sign(key, actor.url <> "#main-key", headers)
case prepare_public_key(keys) do
{:ok, key} ->
HTTPSignatures.sign(key, actor.url <> "#main-key", headers)
{:error, :pem_decode_error} ->
raise ArgumentError, message: "Failed to prepare public keys for #{actor.url}"
end
end
@@ -129,7 +133,7 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
@spec generate_date_header(NaiveDateTime.t()) :: String.t()
def generate_date_header(%NaiveDateTime{} = date) do
Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT")
Timex.lformat!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en")
end
@spec generate_request_target(String.t(), String.t()) :: String.t()

View File

@@ -48,6 +48,7 @@ defmodule Mobilizon.Web.Plugs.HTTPSignatures do
signature_valid = HTTPSignatures.validate_conn(conn)
Logger.debug("Is signature valid ? #{inspect(signature_valid)}")
date_valid = date_valid?(conn)
Logger.debug("Is date valid ? #{inspect(date_valid)}")
assign(conn, :valid_signature, signature_valid && date_valid)
end
end

View File

@@ -48,9 +48,10 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
# if this has payload make sure it is signed by the same actor that made it
def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = conn, _opts) do
with actor_id <- Utils.get_url(actor),
with actor_id when actor_id != nil <- Utils.get_url(actor),
{:actor, %Actor{} = actor} <- {:actor, actor_from_key_id(conn)},
{:actor_match, true} <- {:actor_match, actor.url == actor_id} do
Logger.debug("Mapped identity to #{actor.url} from actor param")
assign(conn, :actor, actor)
else
{:actor_match, false} ->
@@ -58,7 +59,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
assign(conn, :valid_signature, false)
# remove me once testsuite uses mapped capabilities instead of what we do now
# TODO: remove me once testsuite uses mapped capabilities instead of what we do now
{:actor, nil} ->
Logger.debug("Failed to map identity from signature (lookup failure)")
Logger.debug("key_id=#{key_id_from_conn(conn)}, actor=#{actor}")
@@ -70,6 +71,7 @@ defmodule Mobilizon.Web.Plugs.MappedSignatureToIdentity do
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
case actor_from_key_id(conn) do
%Actor{} = actor ->
Logger.debug("Mapped identity to #{actor.url} from signed fetch")
assign(conn, :actor, actor)
_ ->

View File

@@ -12,6 +12,7 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
alias Mobilizon.Resources.Resource
alias Mobilizon.Storage.Page
alias Mobilizon.Todos.TodoList
require Logger
@private_visibility_empty_collection %{elements: [], total: 0}
@json_ld_header Utils.make_json_ld_header()
@@ -39,9 +40,16 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
page = Map.get(args, :page, 1)
collection_name = String.trim_trailing(view_name, ".json")
collection_name = String.to_existing_atom(collection_name)
actor_applicant = Map.get(args, :actor_applicant)
Logger.debug("Rendering actor collection #{inspect(collection_name)}")
Logger.debug(
"Using authenticated fetch with actor #{if actor_applicant, do: actor_applicant.url, else: nil}"
)
%{total: total, elements: elements} =
if can_get_collection?(collection_name, actor, Map.get(args, :actor_applicant)),
if can_get_collection?(collection_name, actor, actor_applicant),
do: fetch_collection(collection_name, actor, page),
else: default_collection(collection_name, actor, page)
@@ -127,8 +135,13 @@ defmodule Mobilizon.Web.ActivityPub.ActorView do
when visibility in [:public, :unlisted] and collection in [:outbox, :followers, :following],
do: true
defp can_get_collection?(_collection_name, %Actor{} = actor, %Actor{} = actor_applicant),
do: actor_applicant_group_member?(actor, actor_applicant)
defp can_get_collection?(_collection_name, %Actor{} = actor, %Actor{} = actor_applicant) do
Logger.debug(
"Testing if #{actor_applicant.url} can be allowed access to #{actor.url} private collections"
)
actor_applicant_group_member?(actor, actor_applicant)
end
defp can_get_collection?(_, _, _), do: false