Simplified HTTP signature processing
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending

This commit is contained in:
Atsuko Karagi 2022-12-19 20:41:48 +00:00 committed by FloatingGhost
parent e17c71a389
commit 4a78c431cf
7 changed files with 105 additions and 24 deletions

View file

@ -21,6 +21,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Non-admin users now cannot register `admin` scope tokens (not security-critical, they didn't work before, but you _could_ create them) - Non-admin users now cannot register `admin` scope tokens (not security-critical, they didn't work before, but you _could_ create them)
- Admin scopes will be dropped on create - Admin scopes will be dropped on create
- Rich media will now backoff for 20 minutes after a failure - Rich media will now backoff for 20 minutes after a failure
- Simplified HTTP signature processing
### Fixed ### Fixed
- /api/v1/accounts/lookup will now respect restrict\_unauthenticated - /api/v1/accounts/lookup will now respect restrict\_unauthenticated

View file

@ -61,7 +61,7 @@ def to_string(scopes), do: Enum.join(scopes, " ")
def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []], def validate(blank_scopes, _app_scopes, _user) when blank_scopes in [nil, []],
do: {:error, :missing_scopes} do: {:error, :missing_scopes}
def validate(scopes, app_scopes, %Pleroma.User{is_admin: is_admin}) do def validate(scopes, app_scopes, _user) do
validate_scopes_are_supported(scopes, app_scopes) validate_scopes_are_supported(scopes, app_scopes)
end end

View file

@ -0,0 +1,31 @@
# Akkoma: Magically expressive social media
# Copyright © 2022-2022 Akkoma Authors <https://akkoma.dev/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureHTTPSignaturePlug do
@moduledoc """
Ensures HTTP signature has been validated by previous plugs on ActivityPub requests.
"""
import Plug.Conn
import Phoenix.Controller, only: [get_format: 1, text: 2]
alias Pleroma.Config
def init(options) do
options
end
def call(%{assigns: %{valid_signature: true}} = conn, _), do: conn
def call(conn, _) do
with true <- get_format(conn) in ["json", "activity+json"],
true <- Config.get([:activitypub, :authorized_fetch_mode], true) do
conn
|> put_status(:unauthorized)
|> text("Request not signed")
|> halt()
else
_ -> conn
end
end
end

View file

@ -4,7 +4,7 @@
defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do 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]
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Web.Router alias Pleroma.Web.Router
alias Pleroma.Signature alias Pleroma.Signature
@ -22,7 +22,7 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
end end
def call(conn, _opts) do def call(conn, _opts) do
if get_format(conn) == "activity+json" do if get_format(conn) in ["json", "activity+json"] do
conn conn
|> maybe_assign_valid_signature() |> maybe_assign_valid_signature()
|> maybe_require_signature() |> maybe_require_signature()
@ -113,18 +113,7 @@ defp maybe_require_signature(
conn conn
end end
defp maybe_require_signature(%{assigns: %{valid_signature: true}} = conn), do: conn defp maybe_require_signature(conn), do: conn
defp maybe_require_signature(conn) do
if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do
conn
|> put_status(:unauthorized)
|> text("Request not signed")
|> halt()
else
conn
end
end
defp signature_host(conn) do defp signature_host(conn) do
with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn),

View file

@ -147,6 +147,7 @@ defmodule Pleroma.Web.Router do
pipeline :http_signature do pipeline :http_signature do
plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug)
plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug)
plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug)
end end
pipeline :static_fe do pipeline :static_fe do

View file

@ -0,0 +1,68 @@
# Akkoma: Magically expressive social media
# Copyright © 2022-2022 Akkoma Authors <https://akkoma.dev/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.Plugs.EnsureHTTPSignaturePlugTest do
use Pleroma.Web.ConnCase
alias Pleroma.Web.Plugs.EnsureHTTPSignaturePlug
import Plug.Conn
import Phoenix.Controller, only: [put_format: 2]
import Pleroma.Tests.Helpers, only: [clear_config: 2]
describe "requires a signature when `authorized_fetch_mode` is enabled" do
setup do
clear_config([:activitypub, :authorized_fetch_mode], true)
conn =
build_conn(:get, "/doesntmatter")
|> put_format("activity+json")
[conn: conn]
end
test "and signature has been set as invalid", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, false)
|> EnsureHTTPSignaturePlug.call(%{})
assert conn.halted == true
assert conn.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
end
test "and signature has been set as valid", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, true)
|> EnsureHTTPSignaturePlug.call(%{})
assert conn.halted == false
end
test "does nothing for non-ActivityPub content types", %{conn: conn} do
conn =
conn
|> assign(:valid_signature, false)
|> put_format("html")
|> EnsureHTTPSignaturePlug.call(%{})
assert conn.halted == false
end
end
test "does nothing on invalid signature when `authorized_fetch_mode` is disabled" do
clear_config([:activitypub, :authorized_fetch_mode], false)
conn =
build_conn(:get, "/doesntmatter")
|> put_format("activity+json")
|> assign(:valid_signature, false)
|> EnsureHTTPSignaturePlug.call(%{})
assert conn.halted == false
end
end

View file

@ -108,10 +108,6 @@ test "and signature is present and incorrect", %{conn: conn} do
|> HTTPSignaturePlug.call(%{}) |> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == false assert conn.assigns.valid_signature == false
assert conn.halted == true
assert conn.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
assert called(HTTPSignatures.validate_conn(:_)) assert called(HTTPSignatures.validate_conn(:_))
end end
@ -125,17 +121,12 @@ test "and signature is correct", %{conn: conn} do
|> HTTPSignaturePlug.call(%{}) |> HTTPSignaturePlug.call(%{})
assert conn.assigns.valid_signature == true assert conn.assigns.valid_signature == true
assert conn.halted == false
assert called(HTTPSignatures.validate_conn(:_)) assert called(HTTPSignatures.validate_conn(:_))
end end
test "and halts the connection when `signature` header is not present", %{conn: conn} do test "and halts the connection when `signature` header is not present", %{conn: conn} do
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.status == 401
assert conn.state == :sent
assert conn.resp_body == "Request not signed"
end end
end end