From aa082ca7b6a64f6cfd509118f76a5c18492e07b9 Mon Sep 17 00:00:00 2001 From: sxsdv1 Date: Sat, 29 Dec 2018 18:01:15 +0100 Subject: [PATCH 1/5] Wire up stub routes for client calls of activitypub inbox/outbox Code style: remove wrapping function of outbox --- .../activity_pub/activity_pub_controller.ex | 36 +++++++++++++++---- lib/pleroma/web/router.ex | 21 +++++++++++ .../activitypub-client-post-activity.json | 9 +++++ .../activity_pub_controller_test.exs | 27 ++++++++++++++ 4 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 test/fixtures/activitypub-client-post-activity.json diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 7fd6a45f5..dfa7eb94b 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -93,19 +93,15 @@ def followers(conn, %{"nickname" => nickname}) do end end - def outbox(conn, %{"nickname" => nickname, "max_id" => max_id}) do + def outbox(conn, %{"nickname" => nickname} = params) do with %User{} = user <- User.get_cached_by_nickname(nickname), {:ok, user} <- Pleroma.Web.WebFinger.ensure_keys_present(user) do conn |> put_resp_header("content-type", "application/activity+json") - |> json(UserView.render("outbox.json", %{user: user, max_id: max_id})) + |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]})) end end - def outbox(conn, %{"nickname" => nickname}) do - outbox(conn, %{"nickname" => nickname, "max_id" => nil}) - end - def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do with %User{} = user <- User.get_cached_by_nickname(nickname), true <- Utils.recipient_in_message(user.ap_id, params), @@ -156,6 +152,34 @@ def relay(conn, _params) do end end + def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do + if nickname == user.nickname do + Logger.info("read inbox #{inspect(params)}") + + conn + |> put_resp_header("content-type", "application/activity+json") + |> json("ok!") + else + conn + |> put_status(:forbidden) + |> json("can't read inbox of #{nickname} as #{user.nickname}") + end + end + + def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do + if nickname == user.nickname do + Logger.info("update outbox #{inspect(params)}") + + conn + |> put_status(:created) + |> json("ok!") + else + conn + |> put_status(:forbidden) + |> json("can't update outbox of #{nickname} as #{user.nickname}") + end + end + def errors(conn, {:error, :not_found}) do conn |> put_status(404) diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 43b04e508..33c573d46 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -412,6 +412,27 @@ defmodule Pleroma.Web.Router do get("/users/:nickname/outbox", ActivityPubController, :outbox) end + pipeline :activitypub_client do + plug(:accepts, ["activity+json"]) + plug(:fetch_session) + plug(Pleroma.Plugs.OAuthPlug) + plug(Pleroma.Plugs.BasicAuthDecoderPlug) + plug(Pleroma.Plugs.UserFetcherPlug) + plug(Pleroma.Plugs.SessionAuthenticationPlug) + plug(Pleroma.Plugs.LegacyAuthenticationPlug) + plug(Pleroma.Plugs.AuthenticationPlug) + plug(Pleroma.Plugs.UserEnabledPlug) + plug(Pleroma.Plugs.SetUserSessionIdPlug) + plug(Pleroma.Plugs.EnsureUserKeyPlug) + end + + scope "/", Pleroma.Web.ActivityPub do + pipe_through([:activitypub_client]) + + get("/users/:nickname/inbox", ActivityPubController, :read_inbox) + post("/users/:nickname/outbox", ActivityPubController, :update_outbox) + end + scope "/relay", Pleroma.Web.ActivityPub do pipe_through(:ap_relay) get("/", ActivityPubController, :relay) diff --git a/test/fixtures/activitypub-client-post-activity.json b/test/fixtures/activitypub-client-post-activity.json new file mode 100644 index 000000000..c985e072b --- /dev/null +++ b/test/fixtures/activitypub-client-post-activity.json @@ -0,0 +1,9 @@ +{ + "@context": ["https://www.w3.org/ns/activitystreams", {"@language": "en-GB"}], + "type": "Create", + "object": { + "type": "Note", + "content": "It's a note" + }, + "to": ["https://www.w3.org/ns/activitystreams#Public"] +} diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 9fdf15505..95027f855 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -112,6 +112,19 @@ test "it inserts an incoming activity into the database", %{conn: conn} do :timer.sleep(500) assert Activity.get_by_ap_id(data["id"]) end + + test "it rejects reads from other users", %{conn: conn} do + user = insert(:user) + otheruser = insert(:user) + + conn = + conn + |> assign(:user, otheruser) + |> put_req_header("accept", "application/activity+json") + |> get("/users/#{user.nickname}/inbox") + + assert json_response(conn, 403) + end end describe "/users/:nickname/outbox" do @@ -138,6 +151,20 @@ test "it returns an announce activity in a collection", %{conn: conn} do assert response(conn, 200) =~ announce_activity.data["object"] end + + test "it rejects posts from other users", %{conn: conn} do + data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() + user = insert(:user) + otheruser = insert(:user) + + conn = + conn + |> assign(:user, otheruser) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/outbox", data) + + assert json_response(conn, 403) + end end describe "/users/:nickname/followers" do From 26dc2dddab6103a3e6e44a3c7ba097283302fc2a Mon Sep 17 00:00:00 2001 From: sxsdv1 Date: Sat, 29 Dec 2018 18:15:28 +0100 Subject: [PATCH 2/5] Implement ActivityPub inbox view More or less verbatim copied from the outbox template with only changes to the activities fetched and url reported --- .../activity_pub/activity_pub_controller.ex | 4 +- .../web/activity_pub/views/user_view.ex | 47 +++++++++++++++++++ .../activity_pub_controller_test.exs | 13 +++++ 3 files changed, 61 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index dfa7eb94b..9f083d0a5 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -154,11 +154,9 @@ def relay(conn, _params) do def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do if nickname == user.nickname do - Logger.info("read inbox #{inspect(params)}") - conn |> put_resp_header("content-type", "application/activity+json") - |> json("ok!") + |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]})) else conn |> put_status(:forbidden) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index f0c268755..439d834e4 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -176,6 +176,53 @@ def render("outbox.json", %{user: user, max_id: max_qid}) do end end + def render("inbox.json", %{user: user, max_id: max_qid}) do + params = %{ + "limit" => "10" + } + + params = + if max_qid != nil do + Map.put(params, "max_id", max_qid) + else + params + end + + activities = ActivityPub.fetch_activities([user.ap_id | user.following], params) + + min_id = Enum.at(Enum.reverse(activities), 0).id + max_id = Enum.at(activities, 0).id + + collection = + Enum.map(activities, fn act -> + {:ok, data} = Transmogrifier.prepare_outgoing(act.data) + data + end) + + iri = "#{user.ap_id}/inbox" + + page = %{ + "id" => "#{iri}?max_id=#{max_id}", + "type" => "OrderedCollectionPage", + "partOf" => iri, + "totalItems" => -1, + "orderedItems" => collection, + "next" => "#{iri}?max_id=#{min_id - 1}" + } + + if max_qid == nil do + %{ + "id" => iri, + "type" => "OrderedCollection", + "totalItems" => -1, + "first" => page + } + |> Map.merge(Utils.make_json_ld_header()) + else + page |> Map.merge(Utils.make_json_ld_header()) + end + end + def collection(collection, iri, page, show_items \\ true, total \\ nil) do offset = (page - 1) * 10 items = Enum.slice(collection, offset, 10) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 95027f855..589645dd6 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -125,6 +125,19 @@ test "it rejects reads from other users", %{conn: conn} do assert json_response(conn, 403) end + + test "it returns a note activity in a collection", %{conn: conn} do + note_activity = insert(:direct_note_activity) + user = User.get_cached_by_ap_id(hd(note_activity.data["to"])) + + conn = + conn + |> assign(:user, user) + |> put_req_header("accept", "application/activity+json") + |> get("/users/#{user.nickname}/inbox") + + assert response(conn, 200) =~ note_activity.data["object"]["content"] + end end describe "/users/:nickname/outbox" do From 569bad821006add1719123f6e2830f23542921d2 Mon Sep 17 00:00:00 2001 From: sxsdv1 Date: Sat, 29 Dec 2018 18:21:45 +0100 Subject: [PATCH 3/5] Create activity when client posts to outbox --- .../activity_pub/activity_pub_controller.ex | 33 ++++++++++++++++--- .../activity_pub_controller_test.exs | 14 ++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 9f083d0a5..dca74db73 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -4,11 +4,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do use Pleroma.Web, :controller - alias Pleroma.{User, Object} + alias Pleroma.{Activity, User, Object} alias Pleroma.Web.ActivityPub.{ObjectView, UserView} alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator require Logger @@ -166,11 +167,33 @@ def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = par def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do if nickname == user.nickname do - Logger.info("update outbox #{inspect(params)}") + actor = user.ap_id() - conn - |> put_status(:created) - |> json("ok!") + params = + params + |> Map.drop(["id"]) + |> Map.put("actor", actor) + |> Transmogrifier.fix_addressing() + + object = + params["object"] + |> Map.merge(Map.take(params, ["to", "cc"])) + |> Map.put("attributedTo", actor) + |> Transmogrifier.fix_object() + + with {:ok, %Activity{} = activity} <- + ActivityPub.create(%{ + to: params["to"], + actor: user, + context: object["context"], + object: object, + additional: Map.take(params, ["cc"]) + }) do + conn + |> put_status(:created) + |> put_resp_header("location", activity.data["id"]) + |> json(%{"id" => activity.data["id"]}) + end else conn |> put_status(:forbidden) diff --git a/test/web/activity_pub/activity_pub_controller_test.exs b/test/web/activity_pub/activity_pub_controller_test.exs index 589645dd6..cb95e0e09 100644 --- a/test/web/activity_pub/activity_pub_controller_test.exs +++ b/test/web/activity_pub/activity_pub_controller_test.exs @@ -178,6 +178,20 @@ test "it rejects posts from other users", %{conn: conn} do assert json_response(conn, 403) end + + test "it inserts an incoming activity into the database", %{conn: conn} do + data = File.read!("test/fixtures/activitypub-client-post-activity.json") |> Poison.decode!() + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> put_req_header("content-type", "application/activity+json") + |> post("/users/#{user.nickname}/outbox", data) + + result = json_response(conn, 201) + assert Activity.get_by_ap_id(result["id"]) + end end describe "/users/:nickname/followers" do From 1e781715c8a9c7f6e24838a5522b960250cffc1b Mon Sep 17 00:00:00 2001 From: sxsdv1 Date: Sat, 29 Dec 2018 18:22:40 +0100 Subject: [PATCH 4/5] Limit activity types accepted to outbox to only 'Create' --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index dca74db73..f90132b4d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -165,7 +165,10 @@ def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = par end end - def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do + def update_outbox( + %{assigns: %{user: user}} = conn, + %{"nickname" => nickname, "type" => "Create"} = params + ) do if nickname == user.nickname do actor = user.ap_id() From f40562b4e1ff213b88c8b6edf57431bfb1b804ac Mon Sep 17 00:00:00 2001 From: sxsdv1 Date: Sun, 30 Dec 2018 11:19:53 +0100 Subject: [PATCH 5/5] Respond with full activity to outbox post --- lib/pleroma/web/activity_pub/activity_pub_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index f90132b4d..e41b14afc 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -195,7 +195,7 @@ def update_outbox( conn |> put_status(:created) |> put_resp_header("location", activity.data["id"]) - |> json(%{"id" => activity.data["id"]}) + |> json(activity.data) end else conn