Simplified HTTP signature processing
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
Some checks are pending
ci/woodpecker/push/woodpecker Pipeline is pending
This commit is contained in:
parent
e17c71a389
commit
4a78c431cf
7 changed files with 105 additions and 24 deletions
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
31
lib/pleroma/web/plugs/ensure_http_signature_plug.ex
Normal file
31
lib/pleroma/web/plugs/ensure_http_signature_plug.ex
Normal 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
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
68
test/pleroma/web/plugs/ensure_http_signature_plug_test.exs
Normal file
68
test/pleroma/web/plugs/ensure_http_signature_plug_test.exs
Normal 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
|
|
@ -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
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue