HTTPSignaturePlugTest: Rewrite to use mox.

This commit is contained in:
Lain Soykaf 2024-05-28 14:00:25 +04:00
parent 3b4be5daa2
commit f5978da676
7 changed files with 144 additions and 113 deletions

View File

@ -155,6 +155,10 @@
config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock config :pleroma, Pleroma.Web.RichMedia.Helpers, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Uploaders.IPFS, config_impl: Pleroma.UnstubbedConfigMock config :pleroma, Pleroma.Uploaders.IPFS, config_impl: Pleroma.UnstubbedConfigMock
config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, config_impl: Pleroma.StaticStubbedConfigMock config :pleroma, Pleroma.Web.Plugs.HTTPSecurityPlug, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug, config_impl: Pleroma.StaticStubbedConfigMock
config :pleroma, Pleroma.Web.Plugs.HTTPSignaturePlug,
http_signatures_impl: Pleroma.StubbedHTTPSignaturesMock
peer_module = peer_module =
if String.to_integer(System.otp_release()) >= 25 do if String.to_integer(System.otp_release()) >= 25 do

View File

@ -0,0 +1,4 @@
defmodule Pleroma.HTTPSignaturesAPI do
@callback validate_conn(conn :: Plug.Conn.t()) :: boolean
@callback signature_for_conn(conn :: Plug.Conn.t()) :: map
end

View File

@ -8,11 +8,17 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
import Plug.Conn import Plug.Conn
import Phoenix.Controller, only: [get_format: 1, text: 2] import Phoenix.Controller, only: [get_format: 1, text: 2]
alias Pleroma.Config
alias Pleroma.Web.ActivityPub.MRF alias Pleroma.Web.ActivityPub.MRF
require Logger require Logger
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
@http_signatures_impl Application.compile_env(
:pleroma,
[__MODULE__, :http_signatures_impl],
HTTPSignatures
)
def init(options) do def init(options) do
options options
end end
@ -41,7 +47,7 @@ defp validate_signature(conn, request_target) do
|> put_req_header("(request-target)", request_target) |> put_req_header("(request-target)", request_target)
|> put_req_header("@request-target", request_target) |> put_req_header("@request-target", request_target)
HTTPSignatures.validate_conn(conn) @http_signatures_impl.validate_conn(conn)
end end
defp validate_signature(conn) do defp validate_signature(conn) do
@ -108,9 +114,9 @@ defp has_signature_header?(conn) do
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn
defp maybe_require_signature(%{remote_ip: remote_ip} = conn) do defp maybe_require_signature(%{remote_ip: remote_ip} = conn) do
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do if @config_impl.get([:activitypub, :authorized_fetch_mode], false) do
exceptions = exceptions =
Pleroma.Config.get([:activitypub, :authorized_fetch_mode_exceptions], []) @config_impl.get([:activitypub, :authorized_fetch_mode_exceptions], [])
|> Enum.map(&InetHelper.parse_cidr/1) |> Enum.map(&InetHelper.parse_cidr/1)
if Enum.any?(exceptions, fn x -> InetCidr.contains?(x, remote_ip) end) do if Enum.any?(exceptions, fn x -> InetCidr.contains?(x, remote_ip) end) do
@ -129,7 +135,8 @@ defp maybe_require_signature(%{remote_ip: remote_ip} = conn) do
defp maybe_filter_requests(%{halted: true} = conn), do: conn defp maybe_filter_requests(%{halted: true} = conn), do: conn
defp maybe_filter_requests(conn) do defp maybe_filter_requests(conn) do
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do if @config_impl.get([:activitypub, :authorized_fetch_mode], false) and
conn.assigns[:actor_id] do
%{host: host} = URI.parse(conn.assigns.actor_id) %{host: host} = URI.parse(conn.assigns.actor_id)
if MRF.subdomain_match?(rejected_domains(), host) do if MRF.subdomain_match?(rejected_domains(), host) do
@ -145,7 +152,7 @@ defp maybe_filter_requests(conn) do
end end
defp rejected_domains do defp rejected_domains do
Config.get([:instance, :rejected_instances]) @config_impl.get([:instance, :rejected_instances])
|> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples()
|> Pleroma.Web.ActivityPub.MRF.subdomains_regex() |> Pleroma.Web.ActivityPub.MRF.subdomains_regex()
end end

View File

@ -3,22 +3,22 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do
use Pleroma.Web.ConnCase use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.Plugs.HTTPSignaturePlug alias Pleroma.Web.Plugs.HTTPSignaturePlug
alias Pleroma.StubbedHTTPSignaturesMock, as: HTTPSignaturesMock
alias Pleroma.StaticStubbedConfigMock, as: ConfigMock
import Plug.Conn import Plug.Conn
import Phoenix.Controller, only: [put_format: 2] import Phoenix.Controller, only: [put_format: 2]
import Mock import Mox
test "it call HTTPSignatures to check validity if the actor signed it" do test "it calls HTTPSignatures to check validity if the actor signed it" do
params = %{"actor" => "http://mastodon.example.org/users/admin"} params = %{"actor" => "http://mastodon.example.org/users/admin"}
conn = build_conn(:get, "/doesntmattter", params) conn = build_conn(:get, "/doesntmattter", params)
with_mock HTTPSignatures, HTTPSignaturesMock
validate_conn: fn _ -> true end, |> expect(:validate_conn, fn _ -> true end)
signature_for_conn: fn _ ->
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
end do
conn = conn =
conn conn
|> put_req_header( |> put_req_header(
@ -30,28 +30,26 @@ test "it call HTTPSignatures to check validity if the actor signed it" do
assert conn.assigns.valid_signature == true assert conn.assigns.valid_signature == true
assert conn.halted == false assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
end end
describe "requires a signature when `authorized_fetch_mode` is enabled" do describe "requires a signature when `authorized_fetch_mode` is enabled" do
setup do setup do
clear_config([:activitypub, :authorized_fetch_mode], true)
params = %{"actor" => "http://mastodon.example.org/users/admin"} params = %{"actor" => "http://mastodon.example.org/users/admin"}
conn = build_conn(:get, "/doesntmattter", params) |> put_format("activity+json") conn = build_conn(:get, "/doesntmattter", params) |> put_format("activity+json")
[conn: conn] [conn: conn]
end end
test "when signature header is present", %{conn: conn} do test "when signature header is present", %{conn: orig_conn} do
with_mock HTTPSignatures, ConfigMock
validate_conn: fn _ -> false end, |> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
signature_for_conn: fn _ -> |> expect(:get, fn [:activitypub, :authorized_fetch_mode_exceptions], [] -> [] end)
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
end do HTTPSignaturesMock
|> expect(:validate_conn, 2, fn _ -> false end)
conn = conn =
conn orig_conn
|> put_req_header( |> put_req_header(
"signature", "signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key" "keyId=\"http://mastodon.example.org/users/admin#main-key"
@ -63,16 +61,15 @@ test "when signature header is present", %{conn: conn} do
assert conn.status == 401 assert conn.status == 401
assert conn.state == :sent assert conn.state == :sent
assert conn.resp_body == "Request not signed" assert conn.resp_body == "Request not signed"
assert called(HTTPSignatures.validate_conn(:_))
end
with_mock HTTPSignatures, ConfigMock
validate_conn: fn _ -> true end, |> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
signature_for_conn: fn _ ->
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"} HTTPSignaturesMock
end do |> expect(:validate_conn, fn _ -> true end)
conn = conn =
conn orig_conn
|> put_req_header( |> put_req_header(
"signature", "signature",
"keyId=\"http://mastodon.example.org/users/admin#main-key" "keyId=\"http://mastodon.example.org/users/admin#main-key"
@ -81,11 +78,13 @@ test "when signature header is present", %{conn: conn} do
assert conn.assigns.valid_signature == true assert conn.assigns.valid_signature == true
assert conn.halted == false assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
end end
test "halts the connection when `signature` header is not present", %{conn: conn} do test "halts the connection when `signature` header is not present", %{conn: conn} do
ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode_exceptions], [] -> [] end)
conn = HTTPSignaturePlug.call(conn, %{}) conn = HTTPSignaturePlug.call(conn, %{})
assert conn.assigns[:valid_signature] == nil assert conn.assigns[:valid_signature] == nil
assert conn.halted == true assert conn.halted == true
@ -95,9 +94,16 @@ test "halts the connection when `signature` header is not present", %{conn: conn
end end
test "exempts specific IPs from `authorized_fetch_mode_exceptions`", %{conn: conn} do test "exempts specific IPs from `authorized_fetch_mode_exceptions`", %{conn: conn} do
clear_config([:activitypub, :authorized_fetch_mode_exceptions], ["192.168.0.0/24"]) ConfigMock
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode_exceptions], [] ->
["192.168.0.0/24"]
end)
|> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
HTTPSignaturesMock
|> expect(:validate_conn, 2, fn _ -> false end)
with_mock HTTPSignatures, validate_conn: fn _ -> false end do
conn = conn =
conn conn
|> Map.put(:remote_ip, {192, 168, 0, 1}) |> Map.put(:remote_ip, {192, 168, 0, 1})
@ -109,20 +115,19 @@ test "exempts specific IPs from `authorized_fetch_mode_exceptions`", %{conn: con
assert conn.remote_ip == {192, 168, 0, 1} assert conn.remote_ip == {192, 168, 0, 1}
assert conn.halted == false assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
end end
end end
test "rejects requests from `rejected_instances` when `authorized_fetch_mode` is enabled" do test "rejects requests from `rejected_instances` when `authorized_fetch_mode` is enabled" do
clear_config([:activitypub, :authorized_fetch_mode], true) ConfigMock
clear_config([:instance, :rejected_instances], [{"mastodon.example.org", "no reason"}]) |> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
|> expect(:get, fn [:instance, :rejected_instances] ->
[{"mastodon.example.org", "no reason"}]
end)
HTTPSignaturesMock
|> expect(:validate_conn, fn _ -> true end)
with_mock HTTPSignatures,
validate_conn: fn _ -> true end,
signature_for_conn: fn _ ->
%{"keyId" => "http://mastodon.example.org/users/admin#main-key"}
end do
conn = conn =
build_conn(:get, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) build_conn(:get, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"})
|> put_req_header( |> put_req_header(
@ -134,14 +139,16 @@ test "rejects requests from `rejected_instances` when `authorized_fetch_mode` is
assert conn.assigns.valid_signature == true assert conn.assigns.valid_signature == true
assert conn.halted == true assert conn.halted == true
assert called(HTTPSignatures.validate_conn(:_))
end
with_mock HTTPSignatures, ConfigMock
validate_conn: fn _ -> true end, |> expect(:get, fn [:activitypub, :authorized_fetch_mode], false -> true end)
signature_for_conn: fn _ -> |> expect(:get, fn [:instance, :rejected_instances] ->
%{"keyId" => "http://allowed.example.org/users/admin#main-key"} [{"mastodon.example.org", "no reason"}]
end do end)
HTTPSignaturesMock
|> expect(:validate_conn, fn _ -> true end)
conn = conn =
build_conn(:get, "/doesntmattter", %{"actor" => "http://allowed.example.org/users/admin"}) build_conn(:get, "/doesntmattter", %{"actor" => "http://allowed.example.org/users/admin"})
|> put_req_header( |> put_req_header(
@ -153,7 +160,5 @@ test "rejects requests from `rejected_instances` when `authorized_fetch_mode` is
assert conn.assigns.valid_signature == true assert conn.assigns.valid_signature == true
assert conn.halted == false assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_))
end
end end
end end

View File

@ -116,6 +116,7 @@ def stub_pipeline do
Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator) Mox.stub_with(Pleroma.Web.FederatorMock, Pleroma.Web.Federator)
Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config) Mox.stub_with(Pleroma.ConfigMock, Pleroma.Config)
Mox.stub_with(Pleroma.StaticStubbedConfigMock, Pleroma.Test.StaticConfig) Mox.stub_with(Pleroma.StaticStubbedConfigMock, Pleroma.Test.StaticConfig)
Mox.stub_with(Pleroma.StubbedHTTPSignaturesMock, Pleroma.Test.HTTPSignaturesProxy)
end end
def ensure_local_uploader(context) do def ensure_local_uploader(context) do

View File

@ -0,0 +1,9 @@
defmodule Pleroma.Test.HTTPSignaturesProxy do
@behaviour Pleroma.HTTPSignaturesAPI
@impl true
defdelegate validate_conn(conn), to: HTTPSignatures
@impl true
defdelegate signature_for_conn(conn), to: HTTPSignatures
end

View File

@ -28,6 +28,7 @@
Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting) Mox.defmock(Pleroma.ConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.UnstubbedConfigMock, for: Pleroma.Config.Getting) Mox.defmock(Pleroma.UnstubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.StaticStubbedConfigMock, for: Pleroma.Config.Getting) Mox.defmock(Pleroma.StaticStubbedConfigMock, for: Pleroma.Config.Getting)
Mox.defmock(Pleroma.StubbedHTTPSignaturesMock, for: Pleroma.HTTPSignaturesAPI)
Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging) Mox.defmock(Pleroma.LoggerMock, for: Pleroma.Logging)