mirror of
https://git.pleroma.social/pleroma/pleroma.git
synced 2025-03-12 22:52:41 +00:00
Merge branch 'pleroma-ensure-authorized-fetch' into security-2.9
This commit is contained in:
commit
4604f2944e
7 changed files with 143 additions and 5 deletions
1
changelog.d/ensure-authorized-fetch.security
Normal file
1
changelog.d/ensure-authorized-fetch.security
Normal file
|
@ -0,0 +1 @@
|
||||||
|
Require HTTP signatures (if enabled) for routes used by both C2S and S2S AP API
|
|
@ -359,7 +359,8 @@ config :pleroma, :activitypub,
|
||||||
follow_handshake_timeout: 500,
|
follow_handshake_timeout: 500,
|
||||||
note_replies_output_limit: 5,
|
note_replies_output_limit: 5,
|
||||||
sign_object_fetches: true,
|
sign_object_fetches: true,
|
||||||
authorized_fetch_mode: false
|
authorized_fetch_mode: false,
|
||||||
|
client_api_enabled: true
|
||||||
|
|
||||||
config :pleroma, :streamer,
|
config :pleroma, :streamer,
|
||||||
workers: 3,
|
workers: 3,
|
||||||
|
|
|
@ -1772,6 +1772,11 @@ config :pleroma, :config_description, [
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Following handshake timeout",
|
description: "Following handshake timeout",
|
||||||
suggestions: [500]
|
suggestions: [500]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :client_api_enabled,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Allow client to server ActivityPub interactions"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
34
lib/pleroma/web/plugs/ap_client_api_enabled_plug.ex
Normal file
34
lib/pleroma/web/plugs/ap_client_api_enabled_plug.ex
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2024 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Plugs.APClientApiEnabledPlug do
|
||||||
|
import Plug.Conn
|
||||||
|
import Phoenix.Controller, only: [text: 2]
|
||||||
|
|
||||||
|
@config_impl Application.compile_env(:pleroma, [__MODULE__, :config_impl], Pleroma.Config)
|
||||||
|
@enabled_path [:activitypub, :client_api_enabled]
|
||||||
|
|
||||||
|
def init(options \\ []), do: Map.new(options)
|
||||||
|
|
||||||
|
def call(conn, %{allow_server: true}) do
|
||||||
|
if @config_impl.get(@enabled_path, false) do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> assign(:user, nil)
|
||||||
|
|> assign(:token, nil)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def call(conn, _) do
|
||||||
|
if @config_impl.get(@enabled_path, false) do
|
||||||
|
conn
|
||||||
|
else
|
||||||
|
conn
|
||||||
|
|> put_status(:forbidden)
|
||||||
|
|> text("C2S not enabled")
|
||||||
|
|> halt()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -19,9 +19,17 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
|
||||||
options
|
options
|
||||||
end
|
end
|
||||||
|
|
||||||
def call(%{assigns: %{valid_signature: true}} = conn, _opts) do
|
def call(%{assigns: %{valid_signature: true}} = conn, _opts), do: conn
|
||||||
|
|
||||||
|
# skip for C2S requests from authenticated users
|
||||||
|
def call(%{assigns: %{user: %Pleroma.User{}}} = conn, _opts) do
|
||||||
|
if get_format(conn) in ["json", "activity+json"] do
|
||||||
|
# ensure access token is provided for 2FA
|
||||||
|
Pleroma.Web.Plugs.EnsureAuthenticatedPlug.call(conn, %{})
|
||||||
|
else
|
||||||
conn
|
conn
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def call(conn, _opts) do
|
def call(conn, _opts) do
|
||||||
if get_format(conn) in ["json", "activity+json"] do
|
if get_format(conn) in ["json", "activity+json"] do
|
||||||
|
|
|
@ -907,22 +907,37 @@ defmodule Pleroma.Web.Router do
|
||||||
# Client to Server (C2S) AP interactions
|
# Client to Server (C2S) AP interactions
|
||||||
pipeline :activitypub_client do
|
pipeline :activitypub_client do
|
||||||
plug(:ap_service_actor)
|
plug(:ap_service_actor)
|
||||||
|
plug(Pleroma.Web.Plugs.APClientApiEnabledPlug)
|
||||||
plug(:fetch_session)
|
plug(:fetch_session)
|
||||||
plug(:authenticate)
|
plug(:authenticate)
|
||||||
plug(:after_auth)
|
plug(:after_auth)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# AP interactions used by both S2S and C2S
|
||||||
|
pipeline :activitypub_server_or_client do
|
||||||
|
plug(:ap_service_actor)
|
||||||
|
plug(:fetch_session)
|
||||||
|
plug(:authenticate)
|
||||||
|
plug(Pleroma.Web.Plugs.APClientApiEnabledPlug, allow_server: true)
|
||||||
|
plug(:after_auth)
|
||||||
|
plug(:http_signature)
|
||||||
|
end
|
||||||
|
|
||||||
scope "/", Pleroma.Web.ActivityPub do
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
pipe_through([:activitypub_client])
|
pipe_through([:activitypub_client])
|
||||||
|
|
||||||
get("/api/ap/whoami", ActivityPubController, :whoami)
|
get("/api/ap/whoami", ActivityPubController, :whoami)
|
||||||
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
|
get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
|
||||||
|
|
||||||
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
|
||||||
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
|
||||||
post("/api/ap/upload_media", ActivityPubController, :upload_media)
|
post("/api/ap/upload_media", ActivityPubController, :upload_media)
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", Pleroma.Web.ActivityPub do
|
||||||
|
pipe_through([:activitypub_server_or_client])
|
||||||
|
|
||||||
|
get("/users/:nickname/outbox", ActivityPubController, :outbox)
|
||||||
|
|
||||||
# The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
|
|
||||||
get("/users/:nickname/followers", ActivityPubController, :followers)
|
get("/users/:nickname/followers", ActivityPubController, :followers)
|
||||||
get("/users/:nickname/following", ActivityPubController, :following)
|
get("/users/:nickname/following", ActivityPubController, :following)
|
||||||
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
get("/users/:nickname/collections/featured", ActivityPubController, :pinned)
|
||||||
|
|
|
@ -1344,6 +1344,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "GET /users/:nickname/outbox" do
|
describe "GET /users/:nickname/outbox" do
|
||||||
|
setup do
|
||||||
|
Mox.stub_with(Pleroma.StaticStubbedConfigMock, Pleroma.Config)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
test "it paginates correctly", %{conn: conn} do
|
test "it paginates correctly", %{conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
conn = assign(conn, :user, user)
|
conn = assign(conn, :user, user)
|
||||||
|
@ -1432,6 +1437,22 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert %{"orderedItems" => []} = resp
|
assert %{"orderedItems" => []} = resp
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does not return a local note activity when C2S API is disabled", %{conn: conn} do
|
||||||
|
clear_config([:activitypub, :client_api_enabled], false)
|
||||||
|
user = insert(:user)
|
||||||
|
reader = insert(:user)
|
||||||
|
{:ok, _note_activity} = CommonAPI.post(user, %{status: "mew mew", visibility: "local"})
|
||||||
|
|
||||||
|
resp =
|
||||||
|
conn
|
||||||
|
|> assign(:user, reader)
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get("/users/#{user.nickname}/outbox?page=true")
|
||||||
|
|> json_response(200)
|
||||||
|
|
||||||
|
assert %{"orderedItems" => []} = resp
|
||||||
|
end
|
||||||
|
|
||||||
test "it returns a note activity in a collection", %{conn: conn} do
|
test "it returns a note activity in a collection", %{conn: conn} do
|
||||||
note_activity = insert(:note_activity)
|
note_activity = insert(:note_activity)
|
||||||
note_object = Object.normalize(note_activity, fetch: false)
|
note_object = Object.normalize(note_activity, fetch: false)
|
||||||
|
@ -1483,6 +1504,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
assert [answer_outbox] = outbox_get["orderedItems"]
|
assert [answer_outbox] = outbox_get["orderedItems"]
|
||||||
assert answer_outbox["id"] == activity.data["id"]
|
assert answer_outbox["id"] == activity.data["id"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it works with authorized fetch forced when authenticated" do
|
||||||
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
outbox_endpoint = user.ap_id <> "/outbox"
|
||||||
|
|
||||||
|
conn =
|
||||||
|
build_conn()
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get(outbox_endpoint)
|
||||||
|
|
||||||
|
assert json_response(conn, 200)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it fails with authorized fetch forced when unauthenticated", %{conn: conn} do
|
||||||
|
clear_config([:activitypub, :authorized_fetch_mode], true)
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
outbox_endpoint = user.ap_id <> "/outbox"
|
||||||
|
|
||||||
|
conn =
|
||||||
|
conn
|
||||||
|
|> put_req_header("accept", "application/activity+json")
|
||||||
|
|> get(outbox_endpoint)
|
||||||
|
|
||||||
|
assert response(conn, 401)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "POST /users/:nickname/outbox (C2S)" do
|
describe "POST /users/:nickname/outbox (C2S)" do
|
||||||
|
@ -2153,6 +2203,30 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|
||||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
||||||
|> json_response(403)
|
|> json_response(403)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "they don't work when C2S API is disabled", %{conn: conn} do
|
||||||
|
clear_config([:activitypub, :client_api_enabled], false)
|
||||||
|
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> get("/api/ap/whoami")
|
||||||
|
|> response(403)
|
||||||
|
|
||||||
|
desc = "Description of the image"
|
||||||
|
|
||||||
|
image = %Plug.Upload{
|
||||||
|
content_type: "image/jpeg",
|
||||||
|
path: Path.absname("test/fixtures/image.jpg"),
|
||||||
|
filename: "an_image.jpg"
|
||||||
|
}
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> assign(:user, user)
|
||||||
|
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
||||||
|
|> response(403)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "pinned collection", %{conn: conn} do
|
test "pinned collection", %{conn: conn} do
|
||||||
|
|
Loading…
Reference in a new issue