Rework HTTPSignatures and fix bridgy interop #874
25 changed files with 363 additions and 518 deletions
|
@ -12,6 +12,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- status and user HTML pages now provide ActivityPub alternate links
|
||||
- the `prune_objects` mix task no longer deletes pinned posts by default
|
||||
- added `--prune-pinned` and `--keep-followed {posts,full,none}` options to the `prune_objects` mix task
|
||||
- timestamps of incoming HTTP signatures are now verified.
|
||||
By default up to two hour old signatures and a maximal clock skew
|
||||
of 40 min for future timestamps or explicit expiry deadlines are accepted
|
||||
|
||||
### Fixed
|
||||
- Internal actors no longer pretend to have unresolvable follow(er|ing) collections
|
||||
|
@ -21,6 +24,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
this lead e.g. to unlisted replies from Pleroma instances being partially treated as private posts
|
||||
- fixed our fetch actor advertising bogus follower and following collection ActivityPub IDs
|
||||
- fix network-path references not being handled by media proxy
|
||||
- federation with bridgy now works
|
||||
- remote signing keys are no longer refreshed multiple times per incoming request
|
||||
|
||||
### Changed
|
||||
- Internal and relay actors are now again represented with type "Application"
|
||||
|
@ -28,6 +33,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|||
- shared inboxes are now generally preferred over personal inboxes, cutting down on duplicate publishing churn
|
||||
- instance actors are now really of type `Service`
|
||||
- ActivityPub delivery attempts are spaced out more giving up after 3h instead of ~20min before
|
||||
- inboxes now fake a succcess reply on incoming Delete documents whose signing key is unknown but gone;
|
||||
this prevents older Mastodon from repeatedly trying to deliver Deletes of actors we never knew anyway
|
||||
|
||||
## 2025.03
|
||||
|
||||
|
|
|
@ -161,7 +161,6 @@ defp cachex_children do
|
|||
build_cachex("banned_urls", default_ttl: :timer.hours(24 * 30), limit: 5_000),
|
||||
build_cachex("translations", default_ttl: :timer.hours(24 * 30), limit: 2500),
|
||||
build_cachex("instances", default_ttl: :timer.hours(24), ttl_interval: 1000, limit: 2500),
|
||||
build_cachex("request_signatures", default_ttl: :timer.hours(24 * 30), limit: 3000),
|
||||
build_cachex("rel_me", default_ttl: :timer.hours(24 * 30), limit: 300),
|
||||
build_cachex("host_meta", default_ttl: :timer.minutes(120), limit: 5000),
|
||||
build_cachex("http_backoff", default_ttl: :timer.hours(24 * 30), limit: 10000)
|
||||
|
|
|
@ -103,7 +103,7 @@ defp load_pack(pack_dir, emoji_groups) do
|
|||
pack_file = Path.join(pack_dir, "pack.json")
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
Logger.info("Loading emoji pack from JSON: #{pack_file}")
|
||||
Logger.debug("Loading emoji pack from JSON: #{pack_file}")
|
||||
contents = Jason.decode!(File.read!(pack_file))
|
||||
|
||||
contents["files"]
|
||||
|
@ -116,13 +116,13 @@ defp load_pack(pack_dir, emoji_groups) do
|
|||
emoji_txt = Path.join(pack_dir, "emoji.txt")
|
||||
|
||||
if File.exists?(emoji_txt) do
|
||||
Logger.info("Loading emoji pack from emoji.txt: #{emoji_txt}")
|
||||
Logger.debug("Loading emoji pack from emoji.txt: #{emoji_txt}")
|
||||
load_from_file(emoji_txt, emoji_groups)
|
||||
else
|
||||
extensions = Config.get([:emoji, :pack_extensions])
|
||||
|
||||
Logger.info(
|
||||
"No emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
|
||||
"No pack.json or emoji.txt found for pack \"#{pack_name}\", assuming all #{Enum.join(extensions, ", ")} files are emoji"
|
||||
)
|
||||
|
||||
make_shortcode_to_file_map(pack_dir, extensions)
|
||||
|
|
|
@ -77,6 +77,12 @@ defp next_backoff_timestamp(%{headers: headers}) when is_list(headers) do
|
|||
|
||||
defp next_backoff_timestamp(_), do: DateTime.utc_now() |> Timex.shift(seconds: 5 * 60)
|
||||
|
||||
defp log_ratelimit(429, host, time),
|
||||
do: Logger.error("Rate limited on #{host}! Backing off until #{time}...")
|
||||
|
||||
defp log_ratelimit(503, host, time),
|
||||
do: Logger.warning("#{host} temporarily unavailable! Backing off until #{time}...")
|
||||
|
||||
# utility function to check the HTTP response for potential backoff headers
|
||||
# will check if we get a 429 or 503 response, and if we do, will back off for a bit
|
||||
@spec check_backoff({:ok | :error, HTTP.Env.t()}, binary()) ::
|
||||
|
@ -84,8 +90,8 @@ defp next_backoff_timestamp(_), do: DateTime.utc_now() |> Timex.shift(seconds: 5
|
|||
defp check_backoff({:ok, env}, host) do
|
||||
case env.status do
|
||||
status when status in [429, 503] ->
|
||||
Logger.error("Rate limited on #{host}! Backing off...")
|
||||
timestamp = next_backoff_timestamp(env)
|
||||
log_ratelimit(status, host, timestamp)
|
||||
ttl = Timex.diff(timestamp, DateTime.utc_now(), :seconds)
|
||||
# we will cache the host for 5 minutes
|
||||
@cachex.put(@backoff_cache, host, true, ttl: ttl)
|
||||
|
|
|
@ -43,6 +43,4 @@ def host(url_or_host) when is_binary(url_or_host) do
|
|||
url_or_host
|
||||
end
|
||||
end
|
||||
|
||||
defdelegate set_request_signatures(url_or_host), to: Instance
|
||||
end
|
||||
|
|
|
@ -26,7 +26,6 @@ defmodule Pleroma.Instances.Instance do
|
|||
field(:favicon, :string)
|
||||
field(:metadata_updated_at, :naive_datetime)
|
||||
field(:nodeinfo, :map, default: %{})
|
||||
field(:has_request_signatures, :boolean)
|
||||
|
||||
timestamps()
|
||||
end
|
||||
|
@ -40,8 +39,7 @@ def changeset(struct, params \\ %{}) do
|
|||
:unreachable_since,
|
||||
:favicon,
|
||||
:nodeinfo,
|
||||
:metadata_updated_at,
|
||||
:has_request_signatures
|
||||
:metadata_updated_at
|
||||
])
|
||||
|> validate_required([:host])
|
||||
|> unique_constraint(:host)
|
||||
|
@ -332,24 +330,4 @@ def get_cached_by_url(url_or_host) do
|
|||
end)
|
||||
end
|
||||
end
|
||||
|
||||
def set_request_signatures(url_or_host) when is_binary(url_or_host) do
|
||||
host = host(url_or_host)
|
||||
existing_record = Repo.get_by(Instance, %{host: host})
|
||||
changes = %{has_request_signatures: true}
|
||||
|
||||
cond do
|
||||
is_nil(existing_record) ->
|
||||
%Instance{}
|
||||
|> changeset(Map.put(changes, :host, host))
|
||||
|> Repo.insert()
|
||||
|
||||
true ->
|
||||
existing_record
|
||||
|> changeset(changes)
|
||||
|> Repo.update()
|
||||
end
|
||||
end
|
||||
|
||||
def set_request_signatures(_), do: {:error, :invalid_input}
|
||||
end
|
||||
|
|
|
@ -233,9 +233,9 @@ defp make_signature(id, date) do
|
|||
signature =
|
||||
InternalFetchActor.get_actor()
|
||||
|> Signature.sign(%{
|
||||
"(request-target)": "get #{uri.path}",
|
||||
host: uri.host,
|
||||
date: date
|
||||
"(request-target)" => "get #{uri.path}",
|
||||
"host" => uri.host,
|
||||
"date" => date
|
||||
})
|
||||
|
||||
{"signature", signature}
|
||||
|
|
|
@ -5,52 +5,71 @@
|
|||
defmodule Pleroma.Signature do
|
||||
@behaviour HTTPSignatures.Adapter
|
||||
|
||||
alias HTTPSignatures.HTTPKey
|
||||
alias Pleroma.User
|
||||
alias Pleroma.User.SigningKey
|
||||
require Logger
|
||||
|
||||
def fetch_public_key(conn) do
|
||||
with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)},
|
||||
{_, {:ok, %SigningKey{} = sk}, _} <-
|
||||
{:fetch, SigningKey.get_or_fetch_by_key_id(kid), kid},
|
||||
def fetch_public_key(kid, _) do
|
||||
with {_, {:ok, %SigningKey{} = sk}} <- {:fetch, SigningKey.get_or_fetch_by_key_id(kid)},
|
||||
{_, {%User{} = key_user, _}} <- {:user, {User.get_by_id(sk.user_id), sk.user_id}},
|
||||
{_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do
|
||||
{:ok, decoded_key}
|
||||
{:ok, %HTTPKey{key: decoded_key, user_data: %{"key_user" => key_user}}}
|
||||
else
|
||||
{:fetch, error, kid} ->
|
||||
Logger.error("Failed to acquire key from signature: #{kid} #{inspect(error)}")
|
||||
{:error, {:fetch, error}}
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
handle_common_errors(e, kid, "acquire")
|
||||
end
|
||||
end
|
||||
|
||||
def refetch_public_key(conn) do
|
||||
with {_, %{"keyId" => kid}} <- {:keyid, HTTPSignatures.signature_for_conn(conn)},
|
||||
{_, {:ok, %SigningKey{} = sk}, _} <- {:fetch, SigningKey.refresh_by_key_id(kid), kid},
|
||||
def refetch_public_key(kid, _) do
|
||||
with {_, {:ok, %SigningKey{} = sk}} <- {:fetch, SigningKey.refresh_by_key_id(kid)},
|
||||
{_, {%User{} = key_user, _}} <- {:user, {User.get_by_id(sk.user_id), sk.user_id}},
|
||||
{_, {:ok, decoded_key}} <- {:decode, SigningKey.public_key_decoded(sk)} do
|
||||
{:ok, decoded_key}
|
||||
{:ok, %HTTPKey{key: decoded_key, user_data: %{"key_user" => key_user}}}
|
||||
else
|
||||
{:fetch, {:error, :too_young}, kid} ->
|
||||
{:fetch, {:error, :too_young}} ->
|
||||
Logger.debug("Refusing to refetch recently updated key: #{kid}")
|
||||
{:error, {:fetch, :too_young}}
|
||||
{:error, {:too_young, kid}}
|
||||
|
||||
{:fetch, {:error, :unknown}, kid} ->
|
||||
{:fetch, {:error, :unknown}} ->
|
||||
Logger.warning("Attempted to refresh unknown key; this should not happen: #{kid}")
|
||||
{:error, {:fetch, :unknown}}
|
||||
{:error, {:unknown, kid}}
|
||||
|
||||
{:fetch, error, kid} ->
|
||||
Logger.error("Failed to refresh stale key from signature: #{kid} #{inspect(error)}")
|
||||
e ->
|
||||
handle_common_errors(e, kid, "refresh stale")
|
||||
end
|
||||
end
|
||||
|
||||
defp handle_common_errors(error, kid, action_name) do
|
||||
case error do
|
||||
{:fetch, {:error, :not_found}} ->
|
||||
{:halt, {:error, :gone}}
|
||||
|
||||
{:fetch, {:reject, reason}} ->
|
||||
{:halt, {:error, {:reject, reason}}}
|
||||
|
||||
{:fetch, error} ->
|
||||
Logger.error("Failed to #{action_name} key from signature: #{kid} #{inspect(error)}")
|
||||
{:error, {:fetch, error}}
|
||||
|
||||
{:user, {_, uid}} ->
|
||||
Logger.warning(
|
||||
"Failed to resolve user (id=#{uid}) for retrieved signing key. Race condition?"
|
||||
)
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
def sign(%User{} = user, headers) do
|
||||
def sign(%User{} = user, headers, opts \\ []) do
|
||||
with {:ok, private_key} <- SigningKey.private_key(user) do
|
||||
HTTPSignatures.sign(private_key, SigningKey.local_key_id(user.ap_id), headers)
|
||||
HTTPSignatures.sign(
|
||||
%HTTPKey{key: private_key},
|
||||
SigningKey.local_key_id(user.ap_id),
|
||||
headers,
|
||||
opts
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -208,7 +208,12 @@ def fetch_remote_key(key_id) do
|
|||
else
|
||||
e ->
|
||||
Logger.debug("Failed to fetch remote key: #{inspect(e)}")
|
||||
{:error, "Could not fetch key"}
|
||||
|
||||
case e do
|
||||
{:error, e} -> {:error, e}
|
||||
{:reject, reason} -> {:reject, reason}
|
||||
_ -> {:error, {"Could not fetch key", e}}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -304,8 +304,6 @@ def outbox(conn, %{"nickname" => nickname}) do
|
|||
|
||||
def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
|
||||
with %User{} = recipient <- User.get_cached_by_nickname(nickname),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
|
||||
true <- Utils.recipient_in_message(recipient, actor, params),
|
||||
params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
|
||||
Federator.incoming_ap_doc(params)
|
||||
json(conn, "ok")
|
||||
|
|
|
@ -57,13 +57,17 @@ def publish_one(
|
|||
date = Pleroma.Signature.signed_date()
|
||||
|
||||
signature =
|
||||
Pleroma.Signature.sign(actor, %{
|
||||
"(request-target)": "post #{path}",
|
||||
host: signature_host(uri),
|
||||
"content-length": byte_size(json),
|
||||
digest: digest,
|
||||
date: date
|
||||
})
|
||||
Pleroma.Signature.sign(
|
||||
actor,
|
||||
%{
|
||||
"(request-target)" => "post #{path}",
|
||||
"host" => signature_host(uri),
|
||||
"content-length" => byte_size(json),
|
||||
"digest" => digest,
|
||||
"date" => date
|
||||
},
|
||||
has_body: true
|
||||
)
|
||||
|
||||
with {:ok, %{status: code}} = result when code in 200..299 <-
|
||||
HTTP.post(
|
||||
|
|
|
@ -76,18 +76,6 @@ def label_in_message?(label, params),
|
|||
[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(User.t(), User.t(), map()) :: boolean()
|
||||
def recipient_in_message(%User{ap_id: ap_id} = recipient, %User{} = actor, params),
|
||||
do:
|
||||
label_in_message?(ap_id, params) || unaddressed_message?(params) ||
|
||||
User.following?(recipient, actor)
|
||||
|
||||
defp extract_list(target) when is_binary(target), do: [target]
|
||||
defp extract_list(lst) when is_list(lst), do: lst
|
||||
defp extract_list(_), do: []
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do
|
||||
@moduledoc """
|
||||
This plug will attempt to pull in a user's public key if we do not have it.
|
||||
We _should_ be able to request the URL from the key URL...
|
||||
"""
|
||||
|
||||
alias Pleroma.User
|
||||
|
||||
def init(options), do: options
|
||||
|
||||
def call(conn, _opts) do
|
||||
key_id = key_id_from_conn(conn)
|
||||
|
||||
unless is_nil(key_id) do
|
||||
User.SigningKey.get_or_fetch_by_key_id(key_id)
|
||||
# now we SHOULD have the user that owns the key locally. maybe.
|
||||
# if we don't, we'll error out when we try to validate.
|
||||
end
|
||||
|
||||
conn
|
||||
end
|
||||
|
||||
defp key_id_from_conn(conn) do
|
||||
case HTTPSignatures.signature_for_conn(conn) do
|
||||
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||
key_id
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
|
@ -6,14 +6,12 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
|||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [get_format: 1]
|
||||
|
||||
alias HTTPSignatures.HTTPKey
|
||||
|
||||
use Pleroma.Web, :verified_routes
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.User.SigningKey
|
||||
require Logger
|
||||
|
||||
@cachex Pleroma.Config.get([:cachex, :provider], Cachex)
|
||||
|
||||
def init(options) do
|
||||
options
|
||||
end
|
||||
|
@ -26,70 +24,109 @@ def call(conn, _opts) do
|
|||
if get_format(conn) in ["json", "activity+json"] do
|
||||
conn
|
||||
|> maybe_assign_valid_signature()
|
||||
|> maybe_require_signature()
|
||||
else
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do
|
||||
def route_aliases(%{path_info: ["objects", id], query_string: query_string, method: method}) do
|
||||
ap_id = url(~p[/objects/#{id}])
|
||||
method = String.downcase(method)
|
||||
|
||||
with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
|
||||
[~p"/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
[
|
||||
fn ->
|
||||
with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
|
||||
["#{method} /notice/#{activity.id}", "#{method} /notice/#{activity.id}?#{query_string}"]
|
||||
else
|
||||
_ -> []
|
||||
end
|
||||
end
|
||||
]
|
||||
end
|
||||
|
||||
def route_aliases(_), do: []
|
||||
|
||||
def maybe_put_created_psudoheader(conn) do
|
||||
case HTTPSignatures.signature_for_conn(conn) do
|
||||
%{"created" => created} ->
|
||||
put_req_header(conn, "(created)", created)
|
||||
defp maybe_log_error(conn, verification_error) do
|
||||
siginfo_str =
|
||||
"<#{conn.method} #{conn.request_path}> #{inspect(get_req_header(conn, "signature"))}"
|
||||
|
||||
_ ->
|
||||
conn
|
||||
case verification_error do
|
||||
:gone ->
|
||||
# We can't verify the data since the actor was deleted and not previously known.
|
||||
# Likely we just received the actor’s Delete activity, so just silently drop.
|
||||
Logger.debug(
|
||||
"Unable to verify request signature of deleted actor; dropping (#{siginfo_str})"
|
||||
)
|
||||
|
||||
{:reject, reason} ->
|
||||
Logger.debug(
|
||||
"Refusing to validate signature from rejected key due to #{inspect(reason)} #{siginfo_str}"
|
||||
)
|
||||
|
||||
:wrong_signature ->
|
||||
Logger.warning("Received request with invalid signature!\n#{inspect(conn)}")
|
||||
|
||||
{:fetch_key, e} ->
|
||||
Logger.info(
|
||||
"Unable to verify request since key cannot be retrieved: #{inspect(e)} #{siginfo_str}"
|
||||
)
|
||||
|
||||
error ->
|
||||
Logger.error(
|
||||
"Failed to verify request signature due to fatal error: #{inspect(error)} #{inspect(conn)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def maybe_put_expires_psudoheader(conn) do
|
||||
case HTTPSignatures.signature_for_conn(conn) do
|
||||
%{"expires" => expires} ->
|
||||
put_req_header(conn, "(expires)", expires)
|
||||
|
||||
_ ->
|
||||
conn
|
||||
end
|
||||
end
|
||||
|
||||
defp assign_valid_signature_on_route_aliases(conn, []), do: conn
|
||||
|
||||
defp assign_valid_signature_on_route_aliases(%{assigns: %{valid_signature: true}} = conn, _),
|
||||
do: conn
|
||||
|
||||
defp assign_valid_signature_on_route_aliases(conn, [path | rest]) do
|
||||
request_target = String.downcase("#{conn.method}") <> " #{path}"
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("(request-target)", request_target)
|
||||
|> maybe_put_created_psudoheader()
|
||||
|> maybe_put_expires_psudoheader()
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, HTTPSignatures.validate_conn(conn))
|
||||
|> assign(:signature_actor_id, signature_host(conn))
|
||||
|> assign_valid_signature_on_route_aliases(rest)
|
||||
end
|
||||
|
||||
defp maybe_halt(conn, :gone) do
|
||||
# If the key was deleted the error is basically unrecoverable.
|
||||
# Most likely it was the Delete activity for the key actor and we never knew about this actor before.
|
||||
# Older Mastodon is very insistent about resending those Deletes until it receives a success.
|
||||
# see: https://github.com/mastodon/mastodon/pull/33617
|
||||
with "POST" <- conn.method,
|
||||
%{"type" => "Delete"} <- conn.body_params do
|
||||
conn
|
||||
|> resp(202, "Accepted")
|
||||
|> halt()
|
||||
else
|
||||
_ -> conn
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_halt(conn, _), do: conn
|
||||
|
||||
defp assign_valid_signature(%{assigns: %{valid_signature: true}} = conn, _),
|
||||
do: conn
|
||||
|
||||
defp assign_valid_signature(conn, request_targets) do
|
||||
case HTTPSignatures.validate_conn(conn, request_targets) do
|
||||
{:ok, %HTTPKey{user_data: ud}} ->
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> assign(:signature_user, ud["key_user"])
|
||||
|
||||
{:error, e} ->
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|> assign(:signature_user, nil)
|
||||
|> maybe_log_error(e)
|
||||
|> maybe_halt(e)
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_assign_valid_signature(conn) do
|
||||
if has_signature_header?(conn) do
|
||||
# set (request-target) header to the appropriate value
|
||||
# we also replace the digest header with the one we computed
|
||||
possible_paths =
|
||||
[conn.request_path, conn.request_path <> "?#{conn.query_string}" | route_aliases(conn)]
|
||||
method = String.downcase(conn.method)
|
||||
|
||||
request_targets =
|
||||
[
|
||||
"#{method} " <> conn.request_path,
|
||||
"#{method} " <> conn.request_path <> "?#{conn.query_string}" | route_aliases(conn)
|
||||
]
|
||||
|
||||
conn =
|
||||
case conn do
|
||||
|
@ -97,7 +134,7 @@ defp maybe_assign_valid_signature(conn) do
|
|||
conn -> conn
|
||||
end
|
||||
|
||||
assign_valid_signature_on_route_aliases(conn, possible_paths)
|
||||
assign_valid_signature(conn, request_targets)
|
||||
else
|
||||
Logger.debug("No signature header!")
|
||||
conn
|
||||
|
@ -107,53 +144,4 @@ defp maybe_assign_valid_signature(conn) do
|
|||
defp has_signature_header?(conn) do
|
||||
conn |> get_req_header("signature") |> Enum.at(0, false)
|
||||
end
|
||||
|
||||
defp maybe_require_signature(
|
||||
%{assigns: %{valid_signature: true, signature_actor_id: actor_id}} = conn
|
||||
) do
|
||||
# inboxes implicitly need http signatures for authentication
|
||||
# so we don't really know if the instance will have broken federation after
|
||||
# we turn on authorized_fetch_mode.
|
||||
#
|
||||
# to "check" this is a signed fetch, verify if method is GET
|
||||
if conn.method == "GET" do
|
||||
actor_host = URI.parse(actor_id).host
|
||||
|
||||
case @cachex.get(:request_signatures_cache, actor_host) do
|
||||
{:ok, nil} ->
|
||||
Logger.debug("Successful signature from #{actor_host}")
|
||||
Instances.set_request_signatures(actor_host)
|
||||
@cachex.put(:request_signatures_cache, actor_host, true)
|
||||
|
||||
{:ok, true} ->
|
||||
:noop
|
||||
|
||||
any ->
|
||||
Logger.warning(
|
||||
"expected request signature cache to return a boolean, instead got #{inspect(any)}"
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
conn
|
||||
end
|
||||
|
||||
defp maybe_require_signature(conn), do: conn
|
||||
|
||||
defp signature_host(conn) do
|
||||
with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)},
|
||||
{:actor_id, actor_id, _} when actor_id != nil <-
|
||||
{:actor_id, SigningKey.key_id_to_ap_id(kid), kid} do
|
||||
actor_id
|
||||
else
|
||||
{:key_id, e} ->
|
||||
Logger.error("Failed to extract key_id from signature: #{inspect(e)}")
|
||||
nil
|
||||
|
||||
{:actor_id, _, kid} ->
|
||||
# SigningKeys SHOULD have been fetched before this gets called!
|
||||
Logger.error("Failed to extract actor_id from signature: signing key #{kid} not known")
|
||||
nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,101 +15,60 @@ def init(options), do: options
|
|||
def call(%{assigns: %{user: %User{}}} = conn, _opts), do: conn
|
||||
|
||||
# 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
|
||||
def call(
|
||||
%{
|
||||
assigns: %{valid_signature: true, signature_user: signature_user},
|
||||
params: %{"actor" => actor}
|
||||
} = conn,
|
||||
_opts
|
||||
) do
|
||||
with actor_id <- Utils.get_ap_id(actor),
|
||||
{:user, %User{} = user} <- {:user, user_from_key_id(conn)},
|
||||
{:federate, true} <- {:federate, should_federate?(user)},
|
||||
{:user_match, true} <- {:user_match, user.ap_id == actor_id} do
|
||||
{:federate, true} <- {:federate, should_federate?(signature_user)},
|
||||
{:user_match, true} <- {:user_match, signature_user.ap_id == actor_id} do
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> assign(:user, signature_user)
|
||||
|> AuthHelper.skip_oauth()
|
||||
else
|
||||
{:user_match, false} ->
|
||||
Logger.debug("Failed to map identity from signature (payload actor mismatch)")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{inspect(actor)}")
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
Logger.debug(
|
||||
"key_user=#{signature_user.id}(#{signature_user.ap_id}), actor=#{inspect(actor)}"
|
||||
)
|
||||
|
||||
# remove me once testsuite uses mapped capabilities instead of what we do now
|
||||
{:user, _} ->
|
||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
||||
assign(conn, :valid_signature, false)
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|
||||
{:federate, false} ->
|
||||
Logger.debug("Identity from signature is instance blocked")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}")
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
error ->
|
||||
handle_common_errors(conn, actor, signature_user, error)
|
||||
end
|
||||
end
|
||||
|
||||
# no payload, probably a signed fetch
|
||||
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
||||
with %User{} = user <- user_from_key_id(conn),
|
||||
{:federate, true} <- {:federate, should_federate?(user)} do
|
||||
def call(%{assigns: %{valid_signature: true, signature_user: signature_user}} = conn, _opts) do
|
||||
with {:federate, true} <- {:federate, should_federate?(signature_user)} do
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> assign(:user, signature_user)
|
||||
|> AuthHelper.skip_oauth()
|
||||
else
|
||||
{:federate, false} ->
|
||||
Logger.debug("Identity from signature is instance blocked")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|
||||
nil ->
|
||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
|
||||
_ ->
|
||||
Logger.debug("Failed to map identity from signature (no payload actor mismatch)")
|
||||
Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}")
|
||||
|
||||
conn
|
||||
|> assign(:valid_signature, false)
|
||||
error -> handle_common_errors(conn, nil, signature_user, error)
|
||||
end
|
||||
end
|
||||
|
||||
# supposedly valid signature but no user (this isn’t supposed to happen)
|
||||
def call(%{assigns: %{valid_signature: true}} = conn, _opts),
|
||||
do: assign(conn, :valid_signature, false)
|
||||
|
||||
# no signature at all
|
||||
def call(conn, _opts), do: conn
|
||||
|
||||
defp key_id_from_conn(conn) do
|
||||
case HTTPSignatures.signature_for_conn(conn) do
|
||||
%{"keyId" => key_id} when is_binary(key_id) ->
|
||||
key_id
|
||||
def handle_common_errors(conn, actor, signature_user, error) do
|
||||
actor_str = if actor == nil, do: "", else: " actor=#{inspect(actor)}"
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
defp user_from_key_id(conn) do
|
||||
with {:key_id, key_id} when is_binary(key_id) <- {:key_id, key_id_from_conn(conn)},
|
||||
{:mapped_ap_id, ap_id} when is_binary(ap_id) <-
|
||||
{:mapped_ap_id, User.SigningKey.key_id_to_ap_id(key_id)},
|
||||
{:user_fetch, {:ok, %User{} = user}} <- {:user_fetch, User.get_or_fetch_by_ap_id(ap_id)} do
|
||||
user
|
||||
else
|
||||
{:key_id, nil} ->
|
||||
Logger.debug("Failed to map identity from signature (no key ID)")
|
||||
{:key_id, nil}
|
||||
|
||||
{:mapped_ap_id, nil} ->
|
||||
Logger.debug("Failed to map identity from signature (could not map key ID to AP ID)")
|
||||
{:mapped_ap_id, nil}
|
||||
|
||||
{:user_fetch, {:error, _}} ->
|
||||
Logger.debug("Failed to map identity from signature (lookup failure)")
|
||||
{:user_fetch, nil}
|
||||
case error do
|
||||
{:federate, false} ->
|
||||
Logger.debug("Identity from signature is instance blocked")
|
||||
Logger.debug("key_user=#{signature_user.nickname}(#{signature_user.id})#{actor_str}")
|
||||
assign(conn, :valid_signature, false)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -146,13 +146,11 @@ defmodule Pleroma.Web.Router do
|
|||
end
|
||||
|
||||
pipeline :optional_http_signature do
|
||||
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||
end
|
||||
|
||||
pipeline :http_signature do
|
||||
plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug)
|
||||
plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
|
||||
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
|
||||
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
|
||||
|
|
2
mix.exs
2
mix.exs
|
@ -162,7 +162,7 @@ defp deps do
|
|||
{:linkify, "~> 0.5.3"},
|
||||
{:http_signatures,
|
||||
git: "https://akkoma.dev/AkkomaGang/http_signatures.git",
|
||||
ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"},
|
||||
ref: "c98a4df78b0dfde15ff1c8b9ce434440be619bf7"},
|
||||
{:telemetry, "~> 1.2"},
|
||||
{:telemetry_poller, "~> 1.0"},
|
||||
{:telemetry_metrics, "~> 0.6"},
|
||||
|
|
2
mix.lock
2
mix.lock
|
@ -58,7 +58,7 @@
|
|||
"hackney": {:hex, :hackney, "1.23.0", "55cc09077112bcb4a69e54be46ed9bc55537763a96cd4a80a221663a7eafd767", [:rebar3], [{:certifi, "~> 2.14.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "6cd1c04cd15c81e5a493f167b226a15f0938a84fc8f0736ebe4ddcab65c0b44e"},
|
||||
"hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
|
||||
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae", [ref: "d44c43d66758c6a73eaa4da9cffdbee0c5da44ae"]},
|
||||
"http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "c98a4df78b0dfde15ff1c8b9ce434440be619bf7", [ref: "c98a4df78b0dfde15ff1c8b9ce434440be619bf7"]},
|
||||
"httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
"igniter": {:hex, :igniter, "0.5.29", "6bf7ddaf15e88ae75f6dad514329530ec8f4721ba14782f6386a7345c1be99fd", [:mix], [{:glob_ex, "~> 0.1.7", [hex: :glob_ex, repo: "hexpm", optional: false]}, {:inflex, "~> 2.0", [hex: :inflex, repo: "hexpm", optional: false]}, {:jason, "~> 1.4", [hex: :jason, repo: "hexpm", optional: false]}, {:owl, "~> 0.11", [hex: :owl, repo: "hexpm", optional: false]}, {:phx_new, "~> 1.7", [hex: :phx_new, repo: "hexpm", optional: true]}, {:req, "~> 0.5", [hex: :req, repo: "hexpm", optional: false]}, {:rewrite, ">= 1.1.1 and < 2.0.0-0", [hex: :rewrite, repo: "hexpm", optional: false]}, {:sourceror, "~> 1.4", [hex: :sourceror, repo: "hexpm", optional: false]}, {:spitfire, ">= 0.1.3 and < 1.0.0-0", [hex: :spitfire, repo: "hexpm", optional: false]}], "hexpm", "beb6e0f69fc6d4e3975ffa26c5459fc63fd96f85cfaeba984c2dfd3d7333b6ad"},
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
defmodule Pleroma.Repo.Migrations.DropInstanceHasRequestSignatures do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
alter table(:instances) do
|
||||
remove(:has_request_signatures, :boolean, default: false, null: false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -4,7 +4,6 @@
|
|||
|
||||
defmodule Pleroma.InstancesTest do
|
||||
alias Pleroma.Instances
|
||||
alias Pleroma.Instances.Instance
|
||||
|
||||
use Pleroma.DataCase
|
||||
|
||||
|
@ -122,21 +121,4 @@ test "keeps unreachable url or host unreachable" do
|
|||
refute Instances.reachable?(host)
|
||||
end
|
||||
end
|
||||
|
||||
describe "set_request_signatures/1" do
|
||||
test "sets instance has request signatures" do
|
||||
host = "domain.com"
|
||||
|
||||
{:ok, instance} = Instances.set_request_signatures(host)
|
||||
assert instance.has_request_signatures
|
||||
|
||||
{:ok, cached_instance} = Instance.get_cached_by_url(host)
|
||||
assert cached_instance.has_request_signatures
|
||||
end
|
||||
|
||||
test "returns error status on non-binary input" do
|
||||
assert {:error, _} = Instances.set_request_signatures(nil)
|
||||
assert {:error, _} = Instances.set_request_signatures(1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,7 +10,9 @@ defmodule Pleroma.SignatureTest do
|
|||
import Tesla.Mock
|
||||
import Mock
|
||||
|
||||
alias HTTPSignatures.HTTPKey
|
||||
alias Pleroma.Signature
|
||||
alias Pleroma.User.SigningKey
|
||||
|
||||
setup do
|
||||
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||
|
@ -28,20 +30,27 @@ defmodule Pleroma.SignatureTest do
|
|||
65_537
|
||||
}
|
||||
|
||||
defp make_fake_signature(key_id), do: "keyId=\"#{key_id}\""
|
||||
defp keyid(user = %Pleroma.User{}), do: keyid(user.ap_id)
|
||||
defp keyid(user_ap_id), do: user_ap_id <> "#main-key"
|
||||
|
||||
defp make_fake_conn(key_id),
|
||||
do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}}
|
||||
defp assert_key(retval, refkey, refuser) do
|
||||
assert match?(
|
||||
{:ok, %HTTPKey{key: ^refkey, user_data: %{"key_user" => %Pleroma.User{}}}},
|
||||
retval
|
||||
)
|
||||
|
||||
{:ok, key} = retval
|
||||
# Avoid comparison failures from (not) loaded Ecto associations etc
|
||||
assert refuser.id == key.user_data["key_user"].id
|
||||
end
|
||||
|
||||
describe "fetch_public_key/1" do
|
||||
test "it returns the key" do
|
||||
expected_result = {:ok, @rsa_public_key}
|
||||
|
||||
user =
|
||||
insert(:user)
|
||||
|> with_signing_key(public_key: @public_key)
|
||||
|
||||
assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result
|
||||
assert_key(Signature.fetch_public_key(keyid(user), nil), @rsa_public_key, user)
|
||||
end
|
||||
|
||||
test "it returns error if public key is nil" do
|
||||
|
@ -50,7 +59,7 @@ test "it returns error if public key is nil" do
|
|||
key_id = user.ap_id <> "#main-key"
|
||||
Tesla.Mock.mock(fn %{url: ^key_id} -> {:ok, %{status: 404}} end)
|
||||
|
||||
assert {:error, _} = Signature.fetch_public_key(make_fake_conn(user.ap_id))
|
||||
assert {:error, _} = Signature.fetch_public_key(keyid(user), nil)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -60,16 +69,17 @@ test "it returns key" do
|
|||
ap_id = "https://mastodon.social/users/lambadalambda"
|
||||
|
||||
%Pleroma.User{signing_key: sk} =
|
||||
user =
|
||||
Pleroma.User.get_or_fetch_by_ap_id(ap_id)
|
||||
|> then(fn {:ok, u} -> u end)
|
||||
|> Pleroma.User.SigningKey.load_key()
|
||||
|> SigningKey.load_key()
|
||||
|
||||
{:ok, _} =
|
||||
%{sk | public_key: "-----BEGIN PUBLIC KEY-----\nasdfghjkl"}
|
||||
|> Ecto.Changeset.change()
|
||||
|> Pleroma.Repo.update()
|
||||
|
||||
assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key}
|
||||
assert_key(Signature.refetch_public_key(keyid(ap_id), nil), @rsa_public_key, user)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -111,8 +121,11 @@ test "it returns signature headers" do
|
|||
|> with_signing_key(private_key: @private_key)
|
||||
|
||||
headers = %{
|
||||
host: "test.test",
|
||||
"content-length": "100"
|
||||
"host" => "test.test",
|
||||
"content-length" => "100",
|
||||
"date" => "Fri, 23 Aug 2019 18:11:24 GMT",
|
||||
"digest" => "SHA-256=a29cdd711788c5118a2256c00d31519e0a5a0d4b144214e012f81e67b80b0ec1",
|
||||
"(request-target)" => "post https://example.com/inbox"
|
||||
}
|
||||
|
||||
assert_signature_equal(
|
||||
|
@ -120,7 +133,7 @@ test "it returns signature headers" do
|
|||
user,
|
||||
headers
|
||||
),
|
||||
"keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\""
|
||||
~s|keyId="https://mastodon.social/users/lambadalambda#main-key",algorithm="rsa-sha256",headers="(request-target) content-length date digest host",signature="fhOT6IBThnCo6rv2Tv8BRXLV7LvVf/7wTX/bbPLtdq5A4GUqrmXUcY5p77jQ6NU9IRIVczeeStxQV6TrHqk/qPdqQOzDcB6cWsSfrB1gsTinBbAWdPzQYqUOTl+Minqn2RERAfPebKYr9QGa0sTODDHvze/UFPuL8a1lDO2VQE0lRCdg49Igr8pGl/CupUx8Fb874omqP0ba3M+siuKEwo02m9hHcbZUeLSN0ZVdvyTMttyqPM1BfwnFXkaQRAblLTyzt4Fv2+fTN+zPipSxJl1YIo1TsmwNq9klqImpjh8NHM3MJ5eZxTZ109S6Q910n1Lm46V/SqByDaYeg9g7Jw=="|
|
||||
)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -555,14 +555,12 @@ test "cached purged after activity deletion", %{conn: conn} do
|
|||
describe "/inbox" do
|
||||
test "it inserts an incoming activity into the database", %{conn: conn} do
|
||||
data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!()
|
||||
{:ok, actor} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://mastodon.example.org/users/admin#main-key\""
|
||||
)
|
||||
|> assign(:signature_user, actor)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
|
@ -592,7 +590,7 @@ test "it inserts an incoming activity into the database" <>
|
|||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, user)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
|
@ -617,7 +615,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do
|
|||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|
||||
|
@ -642,7 +640,7 @@ test "accept follow activity", %{conn: conn} do
|
|||
assert "ok" ==
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}#main-key\"")
|
||||
|> assign(:signature_user, followed_relay)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", accept)
|
||||
|> json_response(200)
|
||||
|
@ -681,10 +679,11 @@ test "accepts Add/Remove activities", %{conn: conn} do
|
|||
actor = "https://example.com/users/lain"
|
||||
key_id = "#{actor}#main-key"
|
||||
|
||||
insert(:user,
|
||||
ap_id: actor,
|
||||
featured_address: "https://example.com/users/lain/collections/featured"
|
||||
)
|
||||
sender =
|
||||
insert(:user,
|
||||
ap_id: actor,
|
||||
featured_address: "https://example.com/users/lain/collections/featured"
|
||||
)
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{
|
||||
|
@ -741,7 +740,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
|
|||
assert "ok" ==
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{actor}#main-key\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|> json_response(200)
|
||||
|
@ -764,7 +763,7 @@ test "accepts Add/Remove activities", %{conn: conn} do
|
|||
assert "ok" ==
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{actor}#main-key\"")
|
||||
|> assign(:signature_user, user)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|> json_response(200)
|
||||
|
@ -863,7 +862,7 @@ test "mastodon pin/unpin", %{conn: conn} do
|
|||
assert "ok" ==
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|> json_response(200)
|
||||
|
@ -883,7 +882,7 @@ test "mastodon pin/unpin", %{conn: conn} do
|
|||
assert "ok" ==
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{actor}#main-key\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/inbox", data)
|
||||
|> json_response(200)
|
||||
|
@ -912,10 +911,12 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da
|
|||
|> Map.put("bcc", [user.ap_id])
|
||||
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
|
||||
|
||||
{:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
|
@ -936,10 +937,12 @@ test "it accepts messages with to as string instead of array", %{conn: conn, dat
|
|||
|> Kernel.put_in(["object", "to"], user.ap_id)
|
||||
|> Kernel.put_in(["object", "cc"], [])
|
||||
|
||||
{:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
|
@ -958,10 +961,12 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
|
|||
|> Kernel.put_in(["object", "to"], [])
|
||||
|> Kernel.put_in(["object", "cc"], user.ap_id)
|
||||
|
||||
{:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
|
@ -985,10 +990,12 @@ test "it accepts messages with bcc as string instead of array", %{conn: conn, da
|
|||
|> Kernel.put_in(["object", "cc"], [])
|
||||
|> Kernel.put_in(["object", "bcc"], user.ap_id)
|
||||
|
||||
{:ok, sender} = User.get_or_fetch_by_ap_id(data["actor"])
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{data["actor"]}#main-key\"")
|
||||
|> assign(:signature_user, sender)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
|
@ -1019,7 +1026,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do
|
|||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{announcer.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, announcer)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
|
@ -1053,7 +1060,7 @@ test "it accepts messages from actors that are followed by the user", %{
|
|||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, actor)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{recipient.nickname}/inbox", data)
|
||||
|
||||
|
@ -1103,7 +1110,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
|
|||
conn =
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, user)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{user.nickname}/inbox", data)
|
||||
|
||||
|
@ -1143,7 +1150,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
|
|||
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, actor)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{recipient.nickname}/inbox", data)
|
||||
|> json_response(200)
|
||||
|
@ -1239,7 +1246,7 @@ test "forwarded report", %{conn: conn} do
|
|||
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"")
|
||||
|> assign(:signature_user, actor)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{reported_user.nickname}/inbox", data)
|
||||
|> json_response(200)
|
||||
|
@ -1260,39 +1267,15 @@ test "forwarded report from mastodon", %{conn: conn} do
|
|||
admin = insert(:user, is_admin: true)
|
||||
actor = insert(:user, local: false)
|
||||
remote_domain = URI.parse(actor.ap_id).host
|
||||
remote_actor = "https://#{remote_domain}/actor"
|
||||
[reported_user, another] = insert_list(2, :user)
|
||||
|
||||
note = insert(:note_activity, user: reported_user)
|
||||
|
||||
Pleroma.Web.CommonAPI.favorite(another, note.id)
|
||||
|
||||
mock_json_body =
|
||||
"test/fixtures/mastodon/application_actor.json"
|
||||
|> File.read!()
|
||||
|> String.replace("{{DOMAIN}}", remote_domain)
|
||||
|
||||
key_url = "#{remote_actor}#main-key"
|
||||
|
||||
Tesla.Mock.mock(fn
|
||||
%{url: ^remote_actor} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: mock_json_body,
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
|
||||
%{url: ^key_url} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: mock_json_body,
|
||||
headers: [{"content-type", "application/activity+json"}]
|
||||
}
|
||||
end)
|
||||
|
||||
data = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"actor" => remote_actor,
|
||||
"actor" => actor.ap_id,
|
||||
"content" => "test report",
|
||||
"id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
|
||||
"object" => [
|
||||
|
@ -1304,7 +1287,7 @@ test "forwarded report from mastodon", %{conn: conn} do
|
|||
|
||||
conn
|
||||
|> assign(:valid_signature, true)
|
||||
|> put_req_header("signature", "keyId=\"#{remote_actor}#main-key\"")
|
||||
|> assign(:signature_user, actor)
|
||||
|> put_req_header("content-type", "application/activity+json")
|
||||
|> post("/users/#{reported_user.nickname}/inbox", data)
|
||||
|> json_response(200)
|
||||
|
|
|
@ -332,74 +332,6 @@ test "fetches last block activities" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "recipient_in_message/3" do
|
||||
test "returns true when recipient in `to`" do
|
||||
recipient = insert(:user)
|
||||
actor = insert(:user)
|
||||
assert Utils.recipient_in_message(recipient, actor, %{"to" => recipient.ap_id})
|
||||
|
||||
assert Utils.recipient_in_message(
|
||||
recipient,
|
||||
actor,
|
||||
%{"to" => [recipient.ap_id], "cc" => ""}
|
||||
)
|
||||
end
|
||||
|
||||
test "returns true when recipient in `cc`" do
|
||||
recipient = insert(:user)
|
||||
actor = insert(:user)
|
||||
assert Utils.recipient_in_message(recipient, actor, %{"cc" => recipient.ap_id})
|
||||
|
||||
assert Utils.recipient_in_message(
|
||||
recipient,
|
||||
actor,
|
||||
%{"cc" => [recipient.ap_id], "to" => ""}
|
||||
)
|
||||
end
|
||||
|
||||
test "returns true when recipient in `bto`" do
|
||||
recipient = insert(:user)
|
||||
actor = insert(:user)
|
||||
assert Utils.recipient_in_message(recipient, actor, %{"bto" => recipient.ap_id})
|
||||
|
||||
assert Utils.recipient_in_message(
|
||||
recipient,
|
||||
actor,
|
||||
%{"bcc" => "", "bto" => [recipient.ap_id]}
|
||||
)
|
||||
end
|
||||
|
||||
test "returns true when recipient in `bcc`" do
|
||||
recipient = insert(:user)
|
||||
actor = insert(:user)
|
||||
assert Utils.recipient_in_message(recipient, actor, %{"bcc" => recipient.ap_id})
|
||||
|
||||
assert Utils.recipient_in_message(
|
||||
recipient,
|
||||
actor,
|
||||
%{"bto" => "", "bcc" => [recipient.ap_id]}
|
||||
)
|
||||
end
|
||||
|
||||
test "returns true when message without addresses fields" do
|
||||
recipient = insert(:user)
|
||||
actor = insert(:user)
|
||||
assert Utils.recipient_in_message(recipient, actor, %{"bccc" => recipient.ap_id})
|
||||
|
||||
assert Utils.recipient_in_message(
|
||||
recipient,
|
||||
actor,
|
||||
%{"btod" => "", "bccc" => [recipient.ap_id]}
|
||||
)
|
||||
end
|
||||
|
||||
test "returns false" do
|
||||
recipient = insert(:user)
|
||||
actor = insert(:user)
|
||||
refute Utils.recipient_in_message(recipient, actor, %{"to" => "ap_id"})
|
||||
end
|
||||
end
|
||||
|
||||
describe "lazy_put_activity_defaults/2" do
|
||||
test "returns map with id and published data" do
|
||||
note_activity = insert(:note_activity)
|
||||
|
|
|
@ -7,13 +7,13 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
@moduletag :mocked
|
||||
import Pleroma.Factory
|
||||
alias Pleroma.Web.Plugs.HTTPSignaturePlug
|
||||
alias Pleroma.Instances.Instance
|
||||
alias Pleroma.Repo
|
||||
|
||||
import Plug.Conn
|
||||
import Phoenix.Controller, only: [put_format: 2]
|
||||
import Mock
|
||||
|
||||
@user_ap_id "http://mastodon.example.org/users/admin"
|
||||
|
||||
setup do
|
||||
user =
|
||||
:user
|
||||
|
@ -26,35 +26,27 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
|
|||
setup_with_mocks([
|
||||
{HTTPSignatures, [],
|
||||
[
|
||||
signature_for_conn: fn _ ->
|
||||
%{
|
||||
"keyId" => "http://mastodon.example.org/users/admin#main-key",
|
||||
"created" => "1234567890",
|
||||
"expires" => "1234567890"
|
||||
}
|
||||
end,
|
||||
validate_conn: fn conn ->
|
||||
Map.get(conn.assigns, :valid_signature, true)
|
||||
validate_conn: fn conn, _ ->
|
||||
cond do
|
||||
Map.get(conn.assigns, :gone_signature_key, false) ->
|
||||
{:error, :gone}
|
||||
|
||||
Map.get(conn.assigns, :rejected_key_id, false) ->
|
||||
{:error, {:reject, :mrf}}
|
||||
|
||||
Map.get(conn.assigns, :valid_signature, true) ->
|
||||
{:ok, user} = Pleroma.User.get_or_fetch_by_ap_id(@user_ap_id)
|
||||
{:ok, %HTTPSignatures.HTTPKey{key: "aaa", user_data: %{"key_user" => user}}}
|
||||
|
||||
true ->
|
||||
{:error, :wrong_signature}
|
||||
end
|
||||
end
|
||||
]}
|
||||
]) do
|
||||
:ok
|
||||
end
|
||||
|
||||
defp submit_to_plug(host), do: submit_to_plug(host, :get, "/doesntmattter")
|
||||
|
||||
defp submit_to_plug(host, method, path) do
|
||||
params = %{"actor" => "http://#{host}/users/admin"}
|
||||
|
||||
build_conn(method, path, params)
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://#{host}/users/admin#main-key"
|
||||
)
|
||||
|> put_format("activity+json")
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
end
|
||||
|
||||
test "it call HTTPSignatures to check validity if the actor signed it", %{user: user} do
|
||||
params = %{"actor" => user.ap_id}
|
||||
conn = build_conn(:get, "/doesntmattter", params)
|
||||
|
@ -69,36 +61,9 @@ test "it call HTTPSignatures to check validity if the actor signed it", %{user:
|
|||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert conn.assigns.signature_actor_id == params["actor"]
|
||||
assert conn.assigns.signature_user.ap_id == params["actor"]
|
||||
assert conn.halted == false
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
end
|
||||
|
||||
test "it sets request signatures property on the instance" do
|
||||
host = "mastodon.example.org"
|
||||
conn = submit_to_plug(host)
|
||||
assert conn.assigns.valid_signature == true
|
||||
instance = Repo.get_by(Instance, %{host: host})
|
||||
assert instance.has_request_signatures
|
||||
end
|
||||
|
||||
test "it does not set request signatures property on the instance when using inbox" do
|
||||
host = "mastodon.example.org"
|
||||
conn = submit_to_plug(host, :post, "/inbox")
|
||||
assert conn.assigns.valid_signature == true
|
||||
|
||||
# we don't even create the instance entry if its just POST /inbox
|
||||
refute Repo.get_by(Instance, %{host: host})
|
||||
end
|
||||
|
||||
test "it does not set request signatures property on the instance when its cached" do
|
||||
host = "mastodon.example.org"
|
||||
Cachex.put(:request_signatures_cache, host, true)
|
||||
conn = submit_to_plug(host)
|
||||
assert conn.assigns.valid_signature == true
|
||||
|
||||
# we don't even create the instance entry if it was already done
|
||||
refute Repo.get_by(Instance, %{host: host})
|
||||
assert called(HTTPSignatures.validate_conn(:_, :_))
|
||||
end
|
||||
|
||||
describe "requires a signature when `authorized_fetch_mode` is enabled" do
|
||||
|
@ -122,7 +87,7 @@ test "and signature is present and incorrect", %{conn: conn} do
|
|||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
assert called(HTTPSignatures.validate_conn(:_, :_))
|
||||
end
|
||||
|
||||
test "and signature is correct", %{conn: conn} do
|
||||
|
@ -135,7 +100,7 @@ test "and signature is correct", %{conn: conn} do
|
|||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
assert conn.assigns.valid_signature == true
|
||||
assert called(HTTPSignatures.validate_conn(:_))
|
||||
assert called(HTTPSignatures.validate_conn(:_, :_))
|
||||
end
|
||||
|
||||
test "and halts the connection when `signature` header is not present", %{conn: conn} do
|
||||
|
@ -151,21 +116,66 @@ test "aliases redirected /object endpoints", _ do
|
|||
path = URI.parse(obj.data["id"]).path
|
||||
conn = build_conn(:get, path, params)
|
||||
|
||||
assert ["/notice/#{act.id}", "/notice/#{act.id}?actor=someparam"] ==
|
||||
HTTPSignaturePlug.route_aliases(conn)
|
||||
aliases =
|
||||
HTTPSignaturePlug.route_aliases(conn)
|
||||
|> Enum.reduce([], fn
|
||||
x, acc when is_binary(x) ->
|
||||
acc ++ [x]
|
||||
|
||||
f, acc when is_function(f) ->
|
||||
add =
|
||||
case f.() do
|
||||
a when is_binary(a) -> [a]
|
||||
a -> a
|
||||
end
|
||||
|
||||
acc ++ add
|
||||
end)
|
||||
|
||||
assert ["get /notice/#{act.id}", "get /notice/#{act.id}?actor=someparam"] == aliases
|
||||
end
|
||||
|
||||
test "(created) psudoheader", _ do
|
||||
conn = build_conn(:get, "/doesntmattter")
|
||||
conn = HTTPSignaturePlug.maybe_put_created_psudoheader(conn)
|
||||
created_header = List.keyfind(conn.req_headers, "(created)", 0)
|
||||
assert {_, "1234567890"} = created_header
|
||||
test "fakes success on gone key when receiving Delete" do
|
||||
build_conn(:post, "/inbox", %{"type" => "Delete"})
|
||||
|> put_format("activity+json")
|
||||
|> assign(:gone_signature_key, true)
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://somewhere.example.org/users/deleted#main-key\""
|
||||
)
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|> response(202)
|
||||
end
|
||||
|
||||
test "(expires) psudoheader", _ do
|
||||
conn = build_conn(:get, "/doesntmattter")
|
||||
conn = HTTPSignaturePlug.maybe_put_expires_psudoheader(conn)
|
||||
expires_header = List.keyfind(conn.req_headers, "(expires)", 0)
|
||||
assert {_, "1234567890"} = expires_header
|
||||
test "fails on gone key for non-Delete" do
|
||||
conn =
|
||||
build_conn(:post, "/inbox", %{"type" => "Note"})
|
||||
|> put_format("activity+json")
|
||||
|> assign(:gone_signature_key, true)
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"http://somewhere.example.org/users/deleted#main-key\""
|
||||
)
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
refute conn.halted
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert conn.assigns.signature_user == nil
|
||||
end
|
||||
|
||||
test "fails on rejected keys", %{user: user} do
|
||||
conn =
|
||||
build_conn(:post, "/inbox", %{"type" => "Note"})
|
||||
|> put_format("activity+json")
|
||||
|> assign(:rejected_key_id, true)
|
||||
|> put_req_header(
|
||||
"signature",
|
||||
"keyId=\"#{user.signing_key.key_id}\""
|
||||
)
|
||||
|> HTTPSignaturePlug.call(%{})
|
||||
|
||||
refute conn.halted
|
||||
assert conn.assigns.valid_signature == false
|
||||
assert conn.assigns.signature_user == nil
|
||||
end
|
||||
end
|
||||
|
|
|
@ -22,16 +22,21 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do
|
|||
{:ok, %{user: user}}
|
||||
end
|
||||
|
||||
defp set_signature(conn, ap_id) do
|
||||
defp set_signature(conn, %Pleroma.User{} = user) do
|
||||
conn
|
||||
|> put_req_header("signature", "keyId=\"#{ap_id}#main-key\"")
|
||||
|> assign(:valid_signature, true)
|
||||
|> assign(:signature_user, user)
|
||||
end
|
||||
|
||||
defp set_signature(conn, ap_id) when is_binary(ap_id) do
|
||||
{:ok, user} = Pleroma.User.get_or_fetch_by_ap_id(ap_id)
|
||||
set_signature(conn, user)
|
||||
end
|
||||
|
||||
test "it successfully maps a valid identity with a valid signature", %{user: user} do
|
||||
conn =
|
||||
build_conn(:get, "/doesntmattter")
|
||||
|> set_signature(user.ap_id)
|
||||
|> set_signature(user)
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
refute is_nil(conn.assigns.user)
|
||||
|
@ -40,7 +45,7 @@ test "it successfully maps a valid identity with a valid signature", %{user: use
|
|||
test "it successfully maps a valid identity with a valid signature with payload", %{user: user} do
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||
|> set_signature(user.ap_id)
|
||||
|> set_signature(user)
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
refute is_nil(conn.assigns.user)
|
||||
|
@ -52,7 +57,9 @@ test "it considers a mapped identity to be invalid when it mismatches a payload"
|
|||
|> set_signature("https://niu.moe/users/rye")
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
assert %{valid_signature: false} == conn.assigns
|
||||
assert conn.assigns.valid_signature == false
|
||||
refute is_nil(conn.assigns.signature_user)
|
||||
refute match?(%{user: _}, conn.assigns)
|
||||
end
|
||||
|
||||
test "it considers a mapped identity to be invalid when the associated instance is blocked", %{
|
||||
|
@ -74,10 +81,12 @@ test "it considers a mapped identity to be invalid when the associated instance
|
|||
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||
|> set_signature(user.ap_id)
|
||||
|> set_signature(user)
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
assert %{valid_signature: false} == conn.assigns
|
||||
assert conn.assigns.valid_signature == false
|
||||
refute is_nil(conn.assigns.signature_user)
|
||||
refute match?(%{user: _}, conn.assigns)
|
||||
end
|
||||
|
||||
test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed",
|
||||
|
@ -97,7 +106,7 @@ test "allowlist federation: it considers a mapped identity to be valid when the
|
|||
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||
|> set_signature(user.ap_id)
|
||||
|> set_signature(user)
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
assert conn.assigns[:valid_signature]
|
||||
|
@ -119,19 +128,11 @@ test "allowlist federation: it considers a mapped identity to be invalid when th
|
|||
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id})
|
||||
|> set_signature(user.ap_id)
|
||||
|> set_signature(user)
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
assert %{valid_signature: false} == conn.assigns
|
||||
end
|
||||
|
||||
@tag skip: "known breakage; the testsuite presently depends on it"
|
||||
test "it considers a mapped identity to be invalid when the identity cannot be found" do
|
||||
conn =
|
||||
build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|
||||
|> set_signature("http://niu.moe/users/rye")
|
||||
|> MappedSignatureToIdentityPlug.call(%{})
|
||||
|
||||
assert %{valid_signature: false} == conn.assigns
|
||||
assert conn.assigns.valid_signature == false
|
||||
refute is_nil(conn.assigns.signature_user)
|
||||
refute match?(%{user: _}, conn.assigns)
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Add table
Reference in a new issue