summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHaelwenn (lanodan) Monnier <contact@hacktivis.me>2020-10-06 17:17:56 +0200
committerHaelwenn (lanodan) Monnier <contact@hacktivis.me>2020-10-06 17:29:01 +0200
commite2bad1efc49e9abd0fb35a1d3fe64cd2e6aa4074 (patch)
tree21eb84da9be6e9671104bfa97435687e322b6436
parent4d852f3e78d6fcfe7bb2ef8b8d3a2f29359dd20c (diff)
AP C2S: Implement proxyUrl endpointfeatures/ap_c2s_proxyUrl
-rw-r--r--lib/pleroma/user.ex15
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex31
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex57
-rw-r--r--lib/pleroma/web/router.ex1
-rw-r--r--test/support/http_request_mock.ex15
-rw-r--r--test/web/activity_pub/activity_pub_controller_test.exs128
-rw-r--r--test/web/activity_pub/views/user_view_test.exs4
7 files changed, 220 insertions, 31 deletions
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 09ea80793..8115fb0fa 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -1770,7 +1770,7 @@ defmodule Pleroma.User do
def fetch_by_ap_id(ap_id, opts \\ []), do: ActivityPub.make_user_from_ap_id(ap_id, opts)
- def get_or_fetch_by_ap_id(ap_id, opts \\ []) do
+ def get_or_fetch_by_ap_id(ap_id, opts \\ []) when is_binary(ap_id) do
cached_user = get_cached_by_ap_id(ap_id)
maybe_fetched_user = needs_update?(cached_user) && fetch_by_ap_id(ap_id, opts)
@@ -1787,6 +1787,13 @@ defmodule Pleroma.User do
end
end
+ def get_or_fetch_by_ap_id!(ap_id, opts \\ []) when is_binary(ap_id) do
+ case get_or_fetch_by_ap_id(ap_id, opts) do
+ {:ok, user} -> user
+ _ -> nil
+ end
+ end
+
@doc """
Creates an internal service actor by URI if missing.
Optionally takes nickname for addressing.
@@ -2097,9 +2104,9 @@ defmodule Pleroma.User do
}
end
- def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
+ def ensure_keys_present(%User{keys: keys} = user) when not is_nil(keys), do: {:ok, user}
- def ensure_keys_present(%User{} = user) do
+ def ensure_keys_present(%User{local: true} = user) do
with {:ok, pem} <- Keys.generate_rsa_pem() do
user
|> cast(%{keys: pem}, [:keys])
@@ -2108,6 +2115,8 @@ defmodule Pleroma.User do
end
end
+ def ensure_keys_present(%User{local: false}), do: {:error, :none}
+
def get_ap_ids_by_nicknames(nicknames) do
from(u in User,
where: u.nickname in ^nicknames,
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index 732c44271..07692221a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -42,7 +42,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
# Note: :following and :followers must be served even without authentication (as via :api)
plug(
EnsureAuthenticatedPlug
- when action in [:read_inbox, :update_outbox, :whoami, :upload_media]
+ when action in [:read_inbox, :update_outbox, :whoami, :upload_media, :proxy_url]
)
plug(
@@ -550,4 +550,33 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
|> json(object.data)
end
end
+
+ def proxy_url(%{assigns: %{user: %User{}}} = conn, %{"id" => id}) when is_binary(id) do
+ cond do
+ object = Object.normalize(id, true) ->
+ conn
+ |> maybe_set_tracking_data(object)
+ |> set_cache_ttl_for(object)
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(ObjectView)
+ |> render("object.json", object: object)
+
+ object = Activity.get_by_ap_id_with_object(id) ->
+ conn
+ |> maybe_set_tracking_data(object)
+ |> set_cache_ttl_for(object)
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(ObjectView)
+ |> render("object.json", object: object)
+
+ user = User.get_or_fetch_by_ap_id!(id) ->
+ conn
+ |> put_resp_content_type("application/activity+json")
+ |> put_view(UserView)
+ |> render("user.json", %{user: user})
+
+ true ->
+ {:error, :not_found}
+ end
+ end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index 3a4564912..715dc4c62 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
use Pleroma.Web, :view
alias Pleroma.Keys
+ alias Pleroma.Maps
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
@@ -15,21 +16,27 @@ defmodule Pleroma.Web.ActivityPub.UserView do
import Ecto.Query
- def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do
+ def render("endpoints.json", %{user: %User{nickname: nil, local: true}}) do
%{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)}
end
- def render("endpoints.json", %{user: %User{local: true} = _user}) do
+ def render("endpoints.json", %{user: %User{local: true}}) do
%{
"oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
"oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
"oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
- "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
+ "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media),
+ "proxyUrl" => Helpers.activity_pub_url(Endpoint, :proxy_url)
}
end
- def render("endpoints.json", _), do: %{}
+ def render("endpoints.json", %{user: %User{shared_inbox: shared_inbox}})
+ when is_binary(shared_inbox) do
+ %{"sharedInbox" => shared_inbox}
+ end
+
+ def render("endpoints.json", _), do: nil
def render("service.json", %{user: user}) do
{:ok, user} = User.ensure_keys_present(user)
@@ -59,6 +66,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"invisible" => User.invisible?(user)
}
|> Map.merge(Utils.make_json_ld_header())
+ |> Maps.put_if_present("endpoints", endpoints)
end
# the instance itself is not a Person, but instead an Application
@@ -68,11 +76,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: %User{nickname: "internal." <> _} = user}),
do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname)
- def render("user.json", %{user: user}) do
- {:ok, user} = User.ensure_keys_present(user)
- {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
- public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
- public_key = :public_key.pem_encode([public_key])
+ def render("user.json", %{user: %User{} = user}) do
+ public_key =
+ with {:ok, user} <- User.ensure_keys_present(user) do
+ {:ok, _, public_key} = Keys.keys_from_pem(user.keys)
+ public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
+ public_key = :public_key.pem_encode([public_key])
+
+ %{
+ "id" => "#{user.ap_id}#main-key",
+ "owner" => user.ap_id,
+ "publicKeyPem" => public_key
+ }
+ else
+ _ -> nil
+ end
+
user = User.sanitize_html(user)
endpoints = render("endpoints.json", %{user: user})
@@ -90,24 +109,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do
%{}
end
+ # FIXME: user.outbox
+ inbox = if user.local, do: "#{user.ap_id}/inbox", else: user.inbox
+ outbox = if user.local, do: "#{user.ap_id}/outbox", else: nil
+
%{
"id" => user.ap_id,
"type" => user.actor_type,
- "following" => "#{user.ap_id}/following",
- "followers" => "#{user.ap_id}/followers",
- "inbox" => "#{user.ap_id}/inbox",
- "outbox" => "#{user.ap_id}/outbox",
+ "following" => User.ap_following(user),
+ "followers" => User.ap_followers(user),
+ "inbox" => inbox,
+ "outbox" => outbox,
"preferredUsername" => user.nickname,
"name" => user.name,
"summary" => user.bio,
"url" => user.ap_id,
"manuallyApprovesFollowers" => user.locked,
- "publicKey" => %{
- "id" => "#{user.ap_id}#main-key",
- "owner" => user.ap_id,
- "publicKeyPem" => public_key
- },
- "endpoints" => endpoints,
"attachment" => fields,
"tag" => emoji_tags,
"discoverable" => user.discoverable,
@@ -116,6 +133,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
|> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user))
|> Map.merge(maybe_make_image(&User.banner_url/2, "image", user))
|> Map.merge(Utils.make_json_ld_header())
+ |> Maps.put_if_present("endpoints", endpoints)
+ |> Maps.put_if_present("publicKey", public_key)
end
def render("following.json", %{user: user, page: page} = opts) do
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index e22b31b4c..2a435c38c 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -625,6 +625,7 @@ defmodule Pleroma.Web.Router do
get("/users/:nickname/outbox", ActivityPubController, :outbox)
post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
post("/api/ap/upload_media", ActivityPubController, :upload_media)
+ post("/api/ap/proxy_url", ActivityPubController, :proxy_url)
# The following two are S2S as well, see `ActivityPub.fetch_follow_information_for_user/1`:
get("/users/:nickname/followers", ActivityPubController, :followers)
diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex
index cb022333f..ba88aff90 100644
--- a/test/support/http_request_mock.ex
+++ b/test/support/http_request_mock.ex
@@ -534,12 +534,15 @@ defmodule HttpRequestMock do
}}
end
- def get(
- "http://mastodon.example.org/@admin/99541947525187367",
- _,
- _,
- _
- ) do
+ def get("http://mastodon.example.org/users/admin/statuses/99541947525187367", _, _, _) do
+ {:ok,
+ %Tesla.Env{
+ status: 200,
+ body: File.read!("test/fixtures/mastodon-note-object.json")
+ }}
+ end
+
+ def get("http://mastodon.example.org/@admin/99541947525187367", _, _, _) do
{:ok,
%Tesla.Env{
status: 200,
diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs
index 0517571f2..bd242dd53 100644
--- a/test/web/activity_pub/activity_pub_controller_test.exs
+++ b/test/web/activity_pub/activity_pub_controller_test.exs
@@ -1546,5 +1546,133 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|> json_response(403)
end
+
+ test "POST /api/ap/proxy_url, get actor", %{conn: conn} do
+ user = insert(:user)
+
+ expected = %{
+ "@context" => [
+ "https://www.w3.org/ns/activitystreams",
+ "http://localhost:4001/schemas/litepub-0.1.jsonld",
+ %{"@language" => "und"}
+ ],
+ "attachment" => [
+ %{"name" => "foo", "type" => "PropertyValue", "value" => "bar"},
+ %{"name" => "foo1", "type" => "PropertyValue", "value" => "bar1"}
+ ],
+ "capabilities" => %{"acceptsChatMessages" => true},
+ "discoverable" => true,
+ "endpoints" => %{"sharedInbox" => "http://mastodon.example.org/inbox"},
+ "followers" => "http://mastodon.example.org/users/admin/followers",
+ "following" => "http://mastodon.example.org/users/admin/following",
+ "icon" => %{
+ "type" => "Image",
+ "url" =>
+ "https://cdn.niu.moe/accounts/avatars/000/033/323/original/fd7f8ae0b3ffedc9.jpeg"
+ },
+ "id" => "http://mastodon.example.org/users/admin",
+ "image" => %{
+ "type" => "Image",
+ "url" =>
+ "https://cdn.niu.moe/accounts/headers/000/033/323/original/850b3448fa5fd477.png"
+ },
+ "inbox" => "http://mastodon.example.org/users/admin/inbox",
+ "manuallyApprovesFollowers" => false,
+ "name" => "admin@mastodon.example.org",
+ "outbox" => nil,
+ "preferredUsername" => "admin@mastodon.example.org",
+ "summary" => "<p></p>",
+ "tag" => [],
+ "type" => "Person",
+ "url" => "http://mastodon.example.org/users/admin"
+ }
+
+ assert conn
+ |> assign(:user, user)
+ |> post("/api/ap/proxy_url", %{"id" => "http://mastodon.example.org/users/admin"})
+ |> json_response(200) == expected
+
+ assert conn
+ |> post("/api/ap/proxy_url", %{"id" => "http://mastodon.example.org/users/admin"})
+ |> json_response(403) == %{"error" => "Invalid credentials."}
+ end
+
+ test "POST /api/ap/proxy_url, get object", %{conn: conn} do
+ user = insert(:user)
+
+ expected = %{
+ "@context" => [
+ "https://www.w3.org/ns/activitystreams",
+ "https://w3id.org/security/v1",
+ %{
+ "Emoji" => "toot:Emoji",
+ "Hashtag" => "as:Hashtag",
+ "atomUri" => "ostatus:atomUri",
+ "conversation" => "ostatus:conversation",
+ "inReplyToAtomUri" => "ostatus:inReplyToAtomUri",
+ "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers",
+ "movedTo" => "as:movedTo",
+ "ostatus" => "http://ostatus.org#",
+ "sensitive" => "as:sensitive",
+ "toot" => "http://joinmastodon.org/ns#"
+ }
+ ],
+ "actor" => "http://mastodon.example.org/users/admin",
+ "atomUri" => "http://mastodon.example.org/users/admin/statuses/99541947525187367",
+ "attachment" => [
+ %{
+ "mediaType" => "image/jpeg",
+ "name" => nil,
+ "type" => "Document",
+ "url" =>
+ "http://mastodon.example.org/system/media_attachments/files/000/000/002/original/334ce029e7bfb920.jpg"
+ }
+ ],
+ "attributedTo" => "http://mastodon.example.org/users/admin",
+ "bcc" => [],
+ "bto" => [],
+ "cc" => ["http://mastodon.example.org/users/admin/followers"],
+ "content" => "<p>yeah.</p>",
+ "context" => "tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation",
+ "conversation" =>
+ "tag:mastodon.example.org,2018-02-17:objectId=59:objectType=Conversation",
+ "id" => "http://mastodon.example.org/users/admin/statuses/99541947525187367",
+ "inReplyTo" => nil,
+ "inReplyToAtomUri" => nil,
+ "published" => "2018-02-17T17:46:20Z",
+ "sensitive" => false,
+ "summary" => "",
+ "tag" => [],
+ "to" => ["https://www.w3.org/ns/activitystreams#Public"],
+ "type" => "Note",
+ "url" => "http://mastodon.example.org/@admin/99541947525187367"
+ }
+
+ assert conn
+ |> assign(:user, user)
+ |> post("/api/ap/proxy_url", %{
+ "id" => "http://mastodon.example.org/users/admin/statuses/99541947525187367"
+ })
+ |> json_response(200) == expected
+
+ assert conn
+ |> post("/api/ap/proxy_url", %{
+ "id" => "http://mastodon.example.org/users/admin/statuses/99541947525187367"
+ })
+ |> json_response(403) == %{"error" => "Invalid credentials."}
+
+ assert conn
+ |> assign(:user, user)
+ |> post("/api/ap/proxy_url", %{
+ "id" => "http://mastodon.example.org/@admin/99541947525187367"
+ })
+ |> json_response(200) == expected
+
+ assert conn
+ |> post("/api/ap/proxy_url", %{
+ "id" => "http://mastodon.example.org/@admin/99541947525187367"
+ })
+ |> json_response(403) == %{"error" => "Invalid credentials."}
+ end
end
end
diff --git a/test/web/activity_pub/views/user_view_test.exs b/test/web/activity_pub/views/user_view_test.exs
index 98c7c9d09..b3af3d3f6 100644
--- a/test/web/activity_pub/views/user_view_test.exs
+++ b/test/web/activity_pub/views/user_view_test.exs
@@ -99,12 +99,12 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do
test "remote users have an empty endpoints structure" do
user = insert(:user, local: false)
- {:ok, user} = User.ensure_keys_present(user)
+ {:error, :none} = User.ensure_keys_present(user)
result = UserView.render("user.json", %{user: user})
assert result["id"] == user.ap_id
- assert result["endpoints"] == %{}
+ assert result["endpoints"] == nil
end
test "instance users do not expose oAuth endpoints" do