Fix sentry issues

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel 2021-04-21 18:57:23 +02:00
parent bdbc473715
commit 67b537f380
No known key found for this signature in database
GPG key ID: A061B9DDE0CA0773
11 changed files with 229 additions and 82 deletions

View file

@ -79,7 +79,6 @@ defmodule Mobilizon.Federation.ActivityPub do
{:ok, struct()} | {:error, any()} {:ok, struct()} | {:error, any()}
def fetch_object_from_url(url, options \\ []) do def fetch_object_from_url(url, options \\ []) do
Logger.info("Fetching object from url #{url}") Logger.info("Fetching object from url #{url}")
force_fetch = Keyword.get(options, :force, false)
with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")}, with {:not_http, true} <- {:not_http, String.starts_with?(url, "http")},
{:existing, nil} <- {:existing, nil} <-
@ -99,9 +98,41 @@ defmodule Mobilizon.Federation.ActivityPub do
Preloader.maybe_preload(entity) Preloader.maybe_preload(entity)
else else
{:existing, entity} -> {:existing, entity} ->
Logger.debug("Entity is already existing") handle_existing_entity(url, entity, options)
e ->
Logger.warn("Something failed while fetching url #{inspect(e)}")
{:error, e}
end
end
@spec handle_existing_entity(String.t(), struct(), Keyword.t()) ::
{:ok, struct()}
| {:ok, struct()}
| {:error, String.t(), struct()}
| {:error, String.t()}
defp handle_existing_entity(url, entity, options) do
Logger.debug("Entity is already existing")
Logger.debug("Going to preload an existing entity")
case refresh_entity(url, entity, options) do
{:ok, entity} ->
Preloader.maybe_preload(entity)
{:error, status, entity} ->
{:ok, entity} = Preloader.maybe_preload(entity)
{:error, status, entity}
err ->
err
end
end
@spec refresh_entity(String.t(), struct(), Keyword.t()) ::
{:ok, struct()} | {:error, String.t(), struct()} | {:error, String.t()}
defp refresh_entity(url, entity, options) do
force_fetch = Keyword.get(options, :force, false)
res =
if force_fetch and not are_same_origin?(url, Endpoint.url()) do if force_fetch and not are_same_origin?(url, Endpoint.url()) do
Logger.debug("Entity is external and we want a force fetch") Logger.debug("Entity is external and we want a force fetch")
@ -114,29 +145,13 @@ defmodule Mobilizon.Federation.ActivityPub do
{:error, "Not found"} -> {:error, "Not found"} ->
{:error, "Not found", entity} {:error, "Not found", entity}
{:error, "Object origin check failed"} ->
{:error, "Object origin check failed"}
end end
else else
{:ok, entity} {:ok, entity}
end end
Logger.debug("Going to preload an existing entity")
case res do
{:ok, entity} ->
Preloader.maybe_preload(entity)
{:error, status, entity} ->
{:ok, entity} = Preloader.maybe_preload(entity)
{:error, status, entity}
err ->
err
end
e ->
Logger.warn("Something failed while fetching url #{inspect(e)}")
{:error, e}
end
end end
@doc """ @doc """
@ -165,8 +180,8 @@ defmodule Mobilizon.Federation.ActivityPub do
{:ok, %Actor{} = actor} -> {:ok, %Actor{} = actor} ->
{:ok, actor} {:ok, actor}
err -> {:error, err} ->
Logger.warn("Could not fetch by AP id") Logger.debug("Could not fetch by AP id")
Logger.debug(inspect(err)) Logger.debug(inspect(err))
{:error, "Could not fetch by AP id"} {:error, "Could not fetch by AP id"}
end end
@ -624,10 +639,6 @@ defmodule Mobilizon.Federation.ActivityPub do
{:error, e} -> {:error, e} ->
Logger.warn("Failed to make actor from url") Logger.warn("Failed to make actor from url")
{:error, e} {:error, e}
e ->
Logger.warn("Failed to make actor from url")
{:error, e}
end end
end end
end end
@ -784,7 +795,7 @@ defmodule Mobilizon.Federation.ActivityPub do
end end
# Fetching a remote actor's information through its AP ID # Fetching a remote actor's information through its AP ID
@spec fetch_and_prepare_actor_from_url(String.t()) :: {:ok, struct()} | {:error, atom()} | any() @spec fetch_and_prepare_actor_from_url(String.t()) :: {:ok, map()} | {:error, atom()} | any()
defp fetch_and_prepare_actor_from_url(url) do defp fetch_and_prepare_actor_from_url(url) do
Logger.debug("Fetching and preparing actor from url") Logger.debug("Fetching and preparing actor from url")
Logger.debug(inspect(url)) Logger.debug(inspect(url))

View file

@ -61,7 +61,7 @@ defmodule Mobilizon.Federation.ActivityPub.Federator do
e -> e ->
# Just drop those for now # Just drop those for now
Logger.error("Unhandled activity") Logger.debug("Unhandled activity")
Logger.debug(inspect(e)) Logger.debug(inspect(e))
Logger.debug(Jason.encode!(params)) Logger.debug(Jason.encode!(params))
end end

View file

@ -30,11 +30,11 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
{:ok, data} {:ok, data}
else else
{:ok, %Tesla.Env{status: 410}} -> {:ok, %Tesla.Env{status: 410}} ->
Logger.warn("Resource at #{url} is 410 Gone") Logger.debug("Resource at #{url} is 410 Gone")
{:error, "Gone"} {:error, "Gone"}
{:ok, %Tesla.Env{status: 404}} -> {:ok, %Tesla.Env{status: 404}} ->
Logger.warn("Resource at #{url} is 404 Gone") Logger.debug("Resource at #{url} is 404 Gone")
{:error, "Not found"} {:error, "Not found"}
{:ok, %Tesla.Env{} = res} -> {:ok, %Tesla.Env{} = res} ->
@ -75,7 +75,7 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
@spec fetch_and_update(String.t(), Keyword.t()) :: {:ok, map(), struct()} @spec fetch_and_update(String.t(), Keyword.t()) :: {:ok, map(), struct()}
def fetch_and_update(url, options \\ []) do def fetch_and_update(url, options \\ []) do
with {:ok, data} when is_map(data) <- fetch(url, options), with {:ok, data} when is_map(data) <- fetch(url, options),
{:origin_check, true} <- {:origin_check, origin_check?(url, data)}, {:origin_check, true} <- {:origin_check, origin_check(url, data)},
params <- %{ params <- %{
"type" => "Update", "type" => "Update",
"to" => data["to"], "to" => data["to"],
@ -87,7 +87,6 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
Transmogrifier.handle_incoming(params) Transmogrifier.handle_incoming(params)
else else
{:origin_check, false} -> {:origin_check, false} ->
Logger.warn("Object origin check failed")
{:error, "Object origin check failed"} {:error, "Object origin check failed"}
{:error, err} -> {:error, err} ->
@ -95,6 +94,17 @@ defmodule Mobilizon.Federation.ActivityPub.Fetcher do
end end
end end
@spec origin_check(String.t(), map()) :: boolean()
defp origin_check(url, data) do
if origin_check?(url, data) do
true
else
Sentry.capture_message("Object origin check failed", extra: %{url: url, data: data})
Logger.debug("Object origin check failed")
false
end
end
@spec address_invalid(String.t()) :: false | {:error, :invalid_url} @spec address_invalid(String.t()) :: false | {:error, :invalid_url}
defp address_invalid(address) do defp address_invalid(address) do
with %URI{host: host, scheme: scheme} <- URI.parse(address), with %URI{host: host, scheme: scheme} <- URI.parse(address),

View file

@ -12,6 +12,7 @@ defmodule Mobilizon.Federation.ActivityPub.Preloader do
alias Mobilizon.Resources.Resource alias Mobilizon.Resources.Resource
alias Mobilizon.Tombstone alias Mobilizon.Tombstone
@spec maybe_preload(struct()) :: {:ok, struct()} | {:error, struct()}
def maybe_preload(%Event{url: url}), def maybe_preload(%Event{url: url}),
do: {:ok, Events.get_public_event_by_url_with_preload!(url)} do: {:ok, Events.get_public_event_by_url_with_preload!(url)}

View file

@ -7,7 +7,6 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
alias Mobilizon.Actors.Actor alias Mobilizon.Actors.Actor
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils} alias Mobilizon.Federation.ActivityPub.{Fetcher, Relay, Transmogrifier, Utils}
alias Mobilizon.Storage.Repo
require Logger require Logger
@doc """ @doc """
@ -60,9 +59,23 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
:ok <- fetch_collection(events_url, on_behalf_of) do :ok <- fetch_collection(events_url, on_behalf_of) do
:ok :ok
else else
{:error, err} ->
Logger.error("Error while refreshing a group")
Sentry.capture_message("Error while refreshing a group",
extra: %{group_url: group_url}
)
Logger.debug(inspect(err))
err -> err ->
Logger.error("Error while refreshing a group") Logger.error("Error while refreshing a group")
Logger.error(inspect(err))
Sentry.capture_message("Error while refreshing a group",
extra: %{group_url: group_url}
)
Logger.debug(inspect(err))
end end
end end
@ -96,14 +109,11 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
end end
end end
@spec refresh_all_external_groups :: any() @spec refresh_all_external_groups :: :ok
def refresh_all_external_groups do def refresh_all_external_groups do
Repo.transaction(fn -> Actors.list_external_groups()
Actors.list_external_groups_for_stream() |> Enum.filter(&Actors.needs_update?/1)
|> Stream.filter(&Actors.needs_update?/1) |> Enum.each(&refresh_profile/1)
|> Stream.map(&refresh_profile/1)
|> Stream.run()
end)
end end
defp process_collection(%{"type" => type, "orderedItems" => items}, _on_behalf_of) defp process_collection(%{"type" => type, "orderedItems" => items}, _on_behalf_of)
@ -122,6 +132,14 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
:ok :ok
end end
# Lemmy uses an OrderedCollection with the items property
defp process_collection(%{"type" => type, "items" => items} = collection, on_behalf_of)
when type in ["OrderedCollection", "OrderedCollectionPage"] do
collection
|> Map.put("orderedItems", items)
|> process_collection(on_behalf_of)
end
defp process_collection(%{"type" => "OrderedCollection", "first" => first}, on_behalf_of) defp process_collection(%{"type" => "OrderedCollection", "first" => first}, on_behalf_of)
when is_map(first), when is_map(first),
do: process_collection(first, on_behalf_of) do: process_collection(first, on_behalf_of)
@ -150,6 +168,11 @@ defmodule Mobilizon.Federation.ActivityPub.Refresher do
Transmogrifier.handle_incoming(data) Transmogrifier.handle_incoming(data)
end end
# If we're handling an announce activity
defp handling_element(%{"type" => "Announce"} = data) do
handling_element(get_in(data, ["object"]))
end
# If we're handling directly an object # If we're handling directly an object
defp handling_element(data) when is_map(data) do defp handling_element(data) when is_map(data) do
object = get_in(data, ["object"]) object = get_in(data, ["object"])

View file

@ -371,11 +371,13 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
end end
end end
def handle_incoming(%{ def handle_incoming(
%{
"type" => "Update", "type" => "Update",
"object" => %{"type" => object_type} = object, "object" => %{"type" => object_type} = object,
"actor" => _actor_id "actor" => _actor_id
}) } = params
)
when object_type in ["Person", "Group", "Application", "Service", "Organization"] do when object_type in ["Person", "Group", "Application", "Service", "Organization"] do
with {:ok, %Actor{suspended: false} = old_actor} <- with {:ok, %Actor{suspended: false} = old_actor} <-
ActivityPub.get_or_fetch_actor_by_url(object["id"]), ActivityPub.get_or_fetch_actor_by_url(object["id"]),
@ -386,7 +388,11 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:ok, activity, new_actor} {:ok, activity, new_actor}
else else
e -> e ->
Logger.error(inspect(e)) Sentry.capture_message("Error while handling an Update activity",
extra: %{params: params}
)
Logger.debug(inspect(e))
:error :error
end end
end end
@ -572,7 +578,8 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
%{"type" => "Delete", "object" => object, "actor" => _actor, "id" => _id} = data %{"type" => "Delete", "object" => object, "actor" => _actor, "id" => _id} = data
) do ) do
with actor_url <- Utils.get_actor(data), with actor_url <- Utils.get_actor(data),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(actor_url), {:actor, {:ok, %Actor{} = actor}} <-
{:actor, ActivityPub.get_or_fetch_actor_by_url(actor_url)},
object_id <- Utils.get_url(object), object_id <- Utils.get_url(object),
{:ok, object} <- is_group_object_gone(object_id), {:ok, object} <- is_group_object_gone(object_id),
{:origin_check, true} <- {:origin_check, true} <-
@ -586,8 +593,25 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
Logger.warn("Object origin check failed") Logger.warn("Object origin check failed")
:error :error
{:actor, {:error, "Could not fetch by AP id"}} ->
{:error, :unknown_actor}
{:error, e} ->
Logger.debug(inspect(e))
# Sentry.capture_message("Error while handling a Delete activity",
# extra: %{data: data}
# )
:error
e -> e ->
Logger.error(inspect(e)) Logger.error(inspect(e))
# Sentry.capture_message("Error while handling a Delete activity",
# extra: %{data: data}
# )
:error :error
end end
end end
@ -610,7 +634,12 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
{:ok, activity, new_resource} {:ok, activity, new_resource}
else else
e -> e ->
Logger.error(inspect(e)) Logger.debug(inspect(e))
Sentry.capture_message("Error while handling an Move activity",
extra: %{data: data}
)
:error :error
end end
end end
@ -741,6 +770,11 @@ defmodule Mobilizon.Federation.ActivityPub.Transmogrifier do
def handle_incoming(object) do def handle_incoming(object) do
Logger.info("Handing something with type #{object["type"]} not supported") Logger.info("Handing something with type #{object["type"]} not supported")
Logger.debug(inspect(object)) Logger.debug(inspect(object))
Sentry.capture_message("Handing something with type #{object["type"]} not supported",
extra: %{object: object}
)
{:error, :not_supported} {:error, :not_supported}
end end

View file

@ -259,7 +259,8 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
are_same_origin?(id, actor) are_same_origin?(id, actor)
end end
def origin_check?(_id, %{"type" => type} = _params) when type in ["Actor", "Group"], do: true def origin_check?(_id, %{"type" => type} = _params) when type in ["Actor", "Person", "Group"],
do: true
def origin_check?(_id, %{"actor" => nil} = _args), do: false def origin_check?(_id, %{"actor" => nil} = _args), do: false
@ -701,4 +702,42 @@ defmodule Mobilizon.Federation.ActivityPub.Utils do
true true
end end
end end
@spec label_in_collection?(any(), any()) :: boolean()
defp label_in_collection?(url, coll) when is_binary(coll), do: url == coll
defp label_in_collection?(url, coll) when is_list(coll), do: url in coll
defp label_in_collection?(_, _), do: false
@spec label_in_message?(String.t(), map()) :: boolean()
def label_in_message?(label, params),
do:
[params["to"], params["cc"], params["bto"], params["bcc"]]
|> Enum.any?(&label_in_collection?(label, &1))
@spec unaddressed_message?(map()) :: boolean()
def unaddressed_message?(params),
do:
[params["to"], params["cc"], params["bto"], params["bcc"]]
|> Enum.all?(&is_nil(&1))
@spec recipient_in_message(Actor.t(), Actor.t(), map()) :: boolean()
def recipient_in_message(%Actor{url: url} = _recipient, %Actor{} = _actor, params),
do: label_in_message?(url, params) || unaddressed_message?(params)
defp extract_list(target) when is_binary(target), do: [target]
defp extract_list(lst) when is_list(lst), do: lst
defp extract_list(_), do: []
def maybe_splice_recipient(url, params) do
need_splice? =
!label_in_collection?(url, params["to"]) &&
!label_in_collection?(url, params["cc"])
if need_splice? do
cc_list = extract_list(params["cc"])
Map.put(params, "cc", [url | cc_list])
else
params
end
end
end end

View file

@ -50,7 +50,8 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
# Gets a public key for a given ActivityPub actor ID (url). # Gets a public key for a given ActivityPub actor ID (url).
@spec get_public_key_for_url(String.t()) :: @spec get_public_key_for_url(String.t()) ::
{:ok, String.t()} | {:error, :actor_fetch_error | :pem_decode_error} {:ok, String.t()}
| {:error, :actor_fetch_error | :pem_decode_error | :actor_not_fetchable}
defp get_public_key_for_url(url) do defp get_public_key_for_url(url) do
with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url), with {:ok, %Actor{keys: keys}} <- ActivityPub.get_or_fetch_actor_by_url(url),
{:ok, public_key} <- prepare_public_key(keys) do {:ok, public_key} <- prepare_public_key(keys) do
@ -61,8 +62,16 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
{:error, :pem_decode_error} {:error, :pem_decode_error}
_ -> {:error, "Could not fetch by AP id"} ->
{:error, :actor_not_fetchable}
err ->
Sentry.capture_message("Unable to fetch actor, so no keys for you",
extra: %{url: url}
)
Logger.error("Unable to fetch actor, so no keys for you") Logger.error("Unable to fetch actor, so no keys for you")
Logger.error(inspect(err))
{:error, :actor_fetch_error} {:error, :actor_fetch_error}
end end
@ -74,9 +83,6 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
:ok <- Logger.debug("Fetching public key for #{actor_id}"), :ok <- Logger.debug("Fetching public key for #{actor_id}"),
{:ok, public_key} <- get_public_key_for_url(actor_id) do {:ok, public_key} <- get_public_key_for_url(actor_id) do
{:ok, public_key} {:ok, public_key}
else
e ->
{:error, e}
end end
end end
@ -87,9 +93,6 @@ defmodule Mobilizon.Federation.HTTPSignatures.Signature do
{:ok, _actor} <- ActivityPub.make_actor_from_url(actor_id), {:ok, _actor} <- ActivityPub.make_actor_from_url(actor_id),
{:ok, public_key} <- get_public_key_for_url(actor_id) do {:ok, public_key} <- get_public_key_for_url(actor_id) do
{:ok, public_key} {:ok, public_key}
else
e ->
{:error, e}
end end
end end

View file

@ -374,12 +374,22 @@ defmodule Mobilizon.Actors do
{:error, remove, error, _} when remove in [:remove_banner, :remove_avatar] -> {:error, remove, error, _} when remove in [:remove_banner, :remove_avatar] ->
Logger.error("Error while deleting actor's banner or avatar") Logger.error("Error while deleting actor's banner or avatar")
Logger.error(inspect(error, pretty: true))
Sentry.capture_message("Error while deleting actor's banner or avatar",
extra: %{err: error}
)
Logger.debug(inspect(error, pretty: true))
{:error, error} {:error, error}
err -> err ->
Logger.error("Unknown error while deleting actor") Logger.error("Unknown error while deleting actor")
Logger.error(inspect(err, pretty: true))
Sentry.capture_message("Error while deleting actor's banner or avatar",
extra: %{err: err}
)
Logger.debug(inspect(err, pretty: true))
{:error, err} {:error, err}
end end
end end
@ -652,10 +662,11 @@ defmodule Mobilizon.Actors do
@doc """ @doc """
Lists the groups. Lists the groups.
""" """
@spec list_groups_for_stream :: Enum.t() @spec list_external_groups(non_neg_integer()) :: list(Actor.t())
def list_external_groups_for_stream do def list_external_groups(limit \\ 100) when limit > 0 do
external_groups_query() external_groups_query()
|> Repo.stream() |> limit(^limit)
|> Repo.all()
end end
@doc """ @doc """

View file

@ -10,7 +10,7 @@ defmodule Mobilizon.Web.ActivityPubController do
alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Actors.{Actor, Member}
alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub
alias Mobilizon.Federation.ActivityPub.Federator alias Mobilizon.Federation.ActivityPub.{Federator, Utils}
alias Mobilizon.Web.ActivityPub.ActorView alias Mobilizon.Web.ActivityPub.ActorView
alias Mobilizon.Web.Cache alias Mobilizon.Web.Cache
@ -105,7 +105,17 @@ defmodule Mobilizon.Web.ActivityPubController do
actor_collection(conn, "outbox", args) actor_collection(conn, "outbox", args)
end end
# TODO: Ensure that this inbox is a recipient of the message def inbox(%{assigns: %{valid_signature: true}} = conn, %{"name" => preferred_username} = params) do
with %Actor{url: recipient_url} = recipient <-
Actors.get_local_actor_by_name(preferred_username),
{:ok, %Actor{} = actor} <- ActivityPub.get_or_fetch_actor_by_url(params["actor"]),
true <- Utils.recipient_in_message(recipient, actor, params),
params <- Utils.maybe_splice_recipient(recipient_url, params) do
Federator.enqueue(:incoming_ap_doc, params)
json(conn, "ok")
end
end
def inbox(%{assigns: %{valid_signature: true}} = conn, params) do def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
Logger.debug("Got something with valid signature inside inbox") Logger.debug("Got something with valid signature inside inbox")
Federator.enqueue(:incoming_ap_doc, params) Federator.enqueue(:incoming_ap_doc, params)
@ -114,7 +124,7 @@ defmodule Mobilizon.Web.ActivityPubController do
# only accept relayed Creates # only accept relayed Creates
def inbox(conn, %{"type" => "Create"} = params) do def inbox(conn, %{"type" => "Create"} = params) do
Logger.info( Logger.debug(
"Signature missing or not from author, relayed Create message, fetching object from source" "Signature missing or not from author, relayed Create message, fetching object from source"
) )
@ -126,8 +136,9 @@ defmodule Mobilizon.Web.ActivityPubController do
def inbox(conn, params) do def inbox(conn, params) do
headers = Enum.into(conn.req_headers, %{}) headers = Enum.into(conn.req_headers, %{})
if String.contains?(headers["signature"], params["actor"]) do if headers["signature"] && params["actor"] &&
Logger.error( String.contains?(headers["signature"], params["actor"]) do
Logger.debug(
"Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!" "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
) )

View file

@ -47,6 +47,10 @@ defmodule Mobilizon.Web.ErrorView do
%{msg: "Not acceptable"} %{msg: "Not acceptable"}
end end
def render("500.json", assigns) do
render("500.html", assigns)
end
def render("500.html", assigns) do def render("500.html", assigns) do
Mobilizon.Config.instance_config() Mobilizon.Config.instance_config()
|> Keyword.get(:default_language, "en") |> Keyword.get(:default_language, "en")