1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.ActivityPub.ActivityPubController do
6 use Pleroma.Web, :controller
10 alias Pleroma.Object.Fetcher
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.InternalFetchActor
14 alias Pleroma.Web.ActivityPub.ObjectView
15 alias Pleroma.Web.ActivityPub.Relay
16 alias Pleroma.Web.ActivityPub.Transmogrifier
17 alias Pleroma.Web.ActivityPub.UserView
18 alias Pleroma.Web.ActivityPub.Utils
19 alias Pleroma.Web.ActivityPub.Visibility
20 alias Pleroma.Web.Federator
24 action_fallback(:errors)
27 Pleroma.Plugs.OAuthScopesPlug,
28 %{scopes: ["read:accounts"]} when action in [:followers, :following]
31 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
32 plug(:set_requester_reachable when action in [:inbox])
33 plug(:relay_active? when action in [:relay])
35 def relay_active?(conn, _) do
36 if Pleroma.Config.get([:instance, :allow_relay]) do
40 |> render_error(:not_found, "not found")
45 def user(conn, %{"nickname" => nickname}) do
46 with %User{} = user <- User.get_cached_by_nickname(nickname),
47 {:ok, user} <- User.ensure_keys_present(user) do
49 |> put_resp_content_type("application/activity+json")
50 |> json(UserView.render("user.json", %{user: user}))
52 nil -> {:error, :not_found}
56 def object(conn, %{"uuid" => uuid}) do
57 with ap_id <- o_status_url(conn, :object, uuid),
58 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
59 {_, true} <- {:public?, Visibility.is_public?(object)} do
61 |> put_resp_content_type("application/activity+json")
62 |> json(ObjectView.render("object.json", %{object: object}))
69 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
70 with ap_id <- o_status_url(conn, :object, uuid),
71 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
72 {_, true} <- {:public?, Visibility.is_public?(object)},
73 likes <- Utils.get_object_likes(object) do
74 {page, _} = Integer.parse(page)
77 |> put_resp_content_type("application/activity+json")
78 |> json(ObjectView.render("likes.json", ap_id, likes, page))
85 def object_likes(conn, %{"uuid" => uuid}) do
86 with ap_id <- o_status_url(conn, :object, uuid),
87 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
88 {_, true} <- {:public?, Visibility.is_public?(object)},
89 likes <- Utils.get_object_likes(object) do
91 |> put_resp_content_type("application/activity+json")
92 |> json(ObjectView.render("likes.json", ap_id, likes))
99 def activity(conn, %{"uuid" => uuid}) do
100 with ap_id <- o_status_url(conn, :activity, uuid),
101 %Activity{} = activity <- Activity.normalize(ap_id),
102 {_, true} <- {:public?, Visibility.is_public?(activity)} do
104 |> put_resp_content_type("application/activity+json")
105 |> json(ObjectView.render("object.json", %{object: activity}))
112 # GET /relay/following
113 def following(%{assigns: %{relay: true}} = conn, _params) do
115 |> put_resp_content_type("application/activity+json")
116 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
119 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
120 with %User{} = user <- User.get_cached_by_nickname(nickname),
121 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
122 {:show_follows, true} <-
123 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
124 {page, _} = Integer.parse(page)
127 |> put_resp_content_type("application/activity+json")
128 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
130 {:show_follows, _} ->
132 |> put_resp_content_type("application/activity+json")
133 |> send_resp(403, "")
137 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
138 with %User{} = user <- User.get_cached_by_nickname(nickname),
139 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
141 |> put_resp_content_type("application/activity+json")
142 |> json(UserView.render("following.json", %{user: user, for: for_user}))
146 # GET /relay/followers
147 def followers(%{assigns: %{relay: true}} = conn, _params) do
149 |> put_resp_content_type("application/activity+json")
150 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
153 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
154 with %User{} = user <- User.get_cached_by_nickname(nickname),
155 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
156 {:show_followers, true} <-
157 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
158 {page, _} = Integer.parse(page)
161 |> put_resp_content_type("application/activity+json")
162 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
164 {:show_followers, _} ->
166 |> put_resp_content_type("application/activity+json")
167 |> send_resp(403, "")
171 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
172 with %User{} = user <- User.get_cached_by_nickname(nickname),
173 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
175 |> put_resp_content_type("application/activity+json")
176 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
180 def outbox(conn, %{"nickname" => nickname} = params) do
181 with %User{} = user <- User.get_cached_by_nickname(nickname),
182 {:ok, user} <- User.ensure_keys_present(user) do
184 |> put_resp_content_type("application/activity+json")
185 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
189 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
190 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
191 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
192 true <- Utils.recipient_in_message(recipient, actor, params),
193 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
194 Federator.incoming_ap_doc(params)
199 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
200 Federator.incoming_ap_doc(params)
204 # only accept relayed Creates
205 def inbox(conn, %{"type" => "Create"} = params) do
207 "Signature missing or not from author, relayed Create message, fetching object from source"
210 Fetcher.fetch_object_from_id(params["object"]["id"])
215 def inbox(conn, params) do
216 headers = Enum.into(conn.req_headers, %{})
218 if String.contains?(headers["signature"], params["actor"]) do
220 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
223 Logger.info(inspect(conn.req_headers))
226 json(conn, dgettext("errors", "error"))
229 defp represent_service_actor(%User{} = user, conn) do
230 with {:ok, user} <- User.ensure_keys_present(user) do
232 |> put_resp_content_type("application/activity+json")
233 |> json(UserView.render("user.json", %{user: user}))
235 nil -> {:error, :not_found}
239 defp represent_service_actor(nil, _), do: {:error, :not_found}
241 def relay(conn, _params) do
243 |> represent_service_actor(conn)
246 def internal_fetch(conn, _params) do
247 InternalFetchActor.get_actor()
248 |> represent_service_actor(conn)
251 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
253 |> put_resp_content_type("application/activity+json")
254 |> json(UserView.render("user.json", %{user: user}))
257 def whoami(_conn, _params), do: {:error, :not_found}
259 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
260 if nickname == user.nickname do
262 |> put_resp_content_type("application/activity+json")
263 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
266 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
268 as_nickname: user.nickname
272 |> put_status(:forbidden)
277 def handle_user_activity(user, %{"type" => "Create"} = params) do
280 |> Map.merge(Map.take(params, ["to", "cc"]))
281 |> Map.put("attributedTo", user.ap_id())
282 |> Transmogrifier.fix_object()
284 ActivityPub.create(%{
287 context: object["context"],
289 additional: Map.take(params, ["cc"])
293 def handle_user_activity(user, %{"type" => "Delete"} = params) do
294 with %Object{} = object <- Object.normalize(params["object"]),
295 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
296 {:ok, delete} <- ActivityPub.delete(object) do
299 _ -> {:error, dgettext("errors", "Can't delete object")}
303 def handle_user_activity(user, %{"type" => "Like"} = params) do
304 with %Object{} = object <- Object.normalize(params["object"]),
305 {:ok, activity, _object} <- ActivityPub.like(user, object) do
308 _ -> {:error, dgettext("errors", "Can't like object")}
312 def handle_user_activity(_, _) do
313 {:error, dgettext("errors", "Unhandled activity type")}
317 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
318 %{"nickname" => nickname} = params
325 |> Map.put("actor", actor)
326 |> Transmogrifier.fix_addressing()
328 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
330 |> put_status(:created)
331 |> put_resp_header("location", activity.data["id"])
332 |> json(activity.data)
336 |> put_status(:bad_request)
341 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
343 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
345 as_nickname: user.nickname
349 |> put_status(:forbidden)
353 def errors(conn, {:error, :not_found}) do
355 |> put_status(:not_found)
356 |> json(dgettext("errors", "Not found"))
359 def errors(conn, _e) do
361 |> put_status(:internal_server_error)
362 |> json(dgettext("errors", "error"))
365 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
366 with actor <- conn.params["actor"],
367 true <- is_binary(actor) do
368 Pleroma.Instances.set_reachable(actor)
374 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
375 {:ok, new_user} = User.ensure_keys_present(user)
378 if new_user != user and match?(%User{}, for_user) do
379 User.get_cached_by_nickname(for_user.nickname)