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
11 alias Pleroma.Object.Fetcher
13 alias Pleroma.Web.ActivityPub.ActivityPub
14 alias Pleroma.Web.ActivityPub.InternalFetchActor
15 alias Pleroma.Web.ActivityPub.ObjectView
16 alias Pleroma.Web.ActivityPub.Relay
17 alias Pleroma.Web.ActivityPub.Transmogrifier
18 alias Pleroma.Web.ActivityPub.UserView
19 alias Pleroma.Web.ActivityPub.Utils
20 alias Pleroma.Web.ActivityPub.Visibility
21 alias Pleroma.Web.Federator
25 action_fallback(:errors)
31 tracking_fun: &Pleroma.Web.ActivityPub.ActivityPubController.track_object_fetch/2
33 when action in [:activity, :object]
36 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
37 plug(:set_requester_reachable when action in [:inbox])
38 plug(:relay_active? when action in [:relay])
40 def relay_active?(conn, _) do
41 if Pleroma.Config.get([:instance, :allow_relay]) do
45 |> render_error(:not_found, "not found")
50 def user(conn, %{"nickname" => nickname}) do
51 with %User{} = user <- User.get_cached_by_nickname(nickname),
52 {:ok, user} <- User.ensure_keys_present(user) do
54 |> put_resp_content_type("application/activity+json")
55 |> json(UserView.render("user.json", %{user: user}))
57 nil -> {:error, :not_found}
61 def object(conn, %{"uuid" => uuid}) do
62 with ap_id <- o_status_url(conn, :object, uuid),
63 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
64 {_, true} <- {:public?, Visibility.is_public?(object)} do
66 |> assign(:tracking_fun_data, object.id)
67 |> set_cache_ttl_for(object)
68 |> put_resp_content_type("application/activity+json")
69 |> put_view(ObjectView)
70 |> render("object.json", object: object)
77 def track_object_fetch(conn, object_id) do
78 case conn.assigns[:user] do
79 %User{id: user_id} -> Delivery.create(object_id, user_id)
86 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
87 with ap_id <- o_status_url(conn, :object, uuid),
88 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
89 {_, true} <- {:public?, Visibility.is_public?(object)},
90 likes <- Utils.get_object_likes(object) do
91 {page, _} = Integer.parse(page)
94 |> put_resp_content_type("application/activity+json")
95 |> json(ObjectView.render("likes.json", ap_id, likes, page))
102 def object_likes(conn, %{"uuid" => uuid}) do
103 with ap_id <- o_status_url(conn, :object, uuid),
104 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
105 {_, true} <- {:public?, Visibility.is_public?(object)},
106 likes <- Utils.get_object_likes(object) do
108 |> put_resp_content_type("application/activity+json")
109 |> json(ObjectView.render("likes.json", ap_id, likes))
116 def activity(conn, %{"uuid" => uuid}) do
117 with ap_id <- o_status_url(conn, :activity, uuid),
118 %Activity{} = activity <- Activity.normalize(ap_id),
119 {_, true} <- {:public?, Visibility.is_public?(activity)} do
121 |> maybe_set_tracking_data(activity)
122 |> set_cache_ttl_for(activity)
123 |> put_resp_content_type("application/activity+json")
124 |> put_view(ObjectView)
125 |> render("object.json", object: activity)
127 {:public?, false} -> {:error, :not_found}
128 nil -> {:error, :not_found}
132 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
133 object_id = Object.normalize(activity).id
134 assign(conn, :tracking_fun_data, object_id)
137 defp maybe_set_tracking_data(conn, _activity), do: assign(conn, :tracking_fun_data, nil)
139 defp set_cache_ttl_for(conn, %Activity{object: object}) do
140 set_cache_ttl_for(conn, object)
143 defp set_cache_ttl_for(conn, entity) do
146 %Object{data: %{"type" => "Question"}} ->
147 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
150 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
156 assign(conn, :cache_ttl, ttl)
159 # GET /relay/following
160 def following(%{assigns: %{relay: true}} = conn, _params) do
162 |> put_resp_content_type("application/activity+json")
163 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
166 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
167 with %User{} = user <- User.get_cached_by_nickname(nickname),
168 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
169 {:show_follows, true} <-
170 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
171 {page, _} = Integer.parse(page)
174 |> put_resp_content_type("application/activity+json")
175 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
177 {:show_follows, _} ->
179 |> put_resp_content_type("application/activity+json")
180 |> send_resp(403, "")
184 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
185 with %User{} = user <- User.get_cached_by_nickname(nickname),
186 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
188 |> put_resp_content_type("application/activity+json")
189 |> json(UserView.render("following.json", %{user: user, for: for_user}))
193 # GET /relay/followers
194 def followers(%{assigns: %{relay: true}} = conn, _params) do
196 |> put_resp_content_type("application/activity+json")
197 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
200 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
201 with %User{} = user <- User.get_cached_by_nickname(nickname),
202 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
203 {:show_followers, true} <-
204 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
205 {page, _} = Integer.parse(page)
208 |> put_resp_content_type("application/activity+json")
209 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
211 {:show_followers, _} ->
213 |> put_resp_content_type("application/activity+json")
214 |> send_resp(403, "")
218 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
219 with %User{} = user <- User.get_cached_by_nickname(nickname),
220 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
222 |> put_resp_content_type("application/activity+json")
223 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
227 def outbox(conn, %{"nickname" => nickname} = params) do
228 with %User{} = user <- User.get_cached_by_nickname(nickname),
229 {:ok, user} <- User.ensure_keys_present(user) do
231 |> put_resp_content_type("application/activity+json")
232 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
236 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
237 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
238 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
239 true <- Utils.recipient_in_message(recipient, actor, params),
240 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
241 Federator.incoming_ap_doc(params)
246 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
247 Federator.incoming_ap_doc(params)
251 # only accept relayed Creates
252 def inbox(conn, %{"type" => "Create"} = params) do
254 "Signature missing or not from author, relayed Create message, fetching object from source"
257 Fetcher.fetch_object_from_id(params["object"]["id"])
262 def inbox(conn, params) do
263 headers = Enum.into(conn.req_headers, %{})
265 if String.contains?(headers["signature"], params["actor"]) do
267 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
270 Logger.info(inspect(conn.req_headers))
273 json(conn, dgettext("errors", "error"))
276 defp represent_service_actor(%User{} = user, conn) do
277 with {:ok, user} <- User.ensure_keys_present(user) do
279 |> put_resp_content_type("application/activity+json")
280 |> json(UserView.render("user.json", %{user: user}))
282 nil -> {:error, :not_found}
286 defp represent_service_actor(nil, _), do: {:error, :not_found}
288 def relay(conn, _params) do
290 |> represent_service_actor(conn)
293 def internal_fetch(conn, _params) do
294 InternalFetchActor.get_actor()
295 |> represent_service_actor(conn)
298 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
300 |> put_resp_content_type("application/activity+json")
301 |> json(UserView.render("user.json", %{user: user}))
304 def whoami(_conn, _params), do: {:error, :not_found}
307 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
308 %{"nickname" => nickname} = params
311 |> put_resp_content_type("application/activity+json")
312 |> put_view(UserView)
313 |> render("inbox.json", user: user, max_id: params["max_id"])
316 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
317 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
320 |> put_status(:forbidden)
324 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
325 "nickname" => nickname
328 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
330 as_nickname: as_nickname
334 |> put_status(:forbidden)
338 def handle_user_activity(user, %{"type" => "Create"} = params) do
341 |> Map.merge(Map.take(params, ["to", "cc"]))
342 |> Map.put("attributedTo", user.ap_id())
343 |> Transmogrifier.fix_object()
345 ActivityPub.create(%{
348 context: object["context"],
350 additional: Map.take(params, ["cc"])
354 def handle_user_activity(user, %{"type" => "Delete"} = params) do
355 with %Object{} = object <- Object.normalize(params["object"]),
356 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
357 {:ok, delete} <- ActivityPub.delete(object) do
360 _ -> {:error, dgettext("errors", "Can't delete object")}
364 def handle_user_activity(user, %{"type" => "Like"} = params) do
365 with %Object{} = object <- Object.normalize(params["object"]),
366 {:ok, activity, _object} <- ActivityPub.like(user, object) do
369 _ -> {:error, dgettext("errors", "Can't like object")}
373 def handle_user_activity(_, _) do
374 {:error, dgettext("errors", "Unhandled activity type")}
378 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
379 %{"nickname" => nickname} = params
386 |> Map.put("actor", actor)
387 |> Transmogrifier.fix_addressing()
389 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
391 |> put_status(:created)
392 |> put_resp_header("location", activity.data["id"])
393 |> json(activity.data)
397 |> put_status(:bad_request)
402 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
404 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
406 as_nickname: user.nickname
410 |> put_status(:forbidden)
414 def errors(conn, {:error, :not_found}) do
416 |> put_status(:not_found)
417 |> json(dgettext("errors", "Not found"))
420 def errors(conn, _e) do
422 |> put_status(:internal_server_error)
423 |> json(dgettext("errors", "error"))
426 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
427 with actor <- conn.params["actor"],
428 true <- is_binary(actor) do
429 Pleroma.Instances.set_reachable(actor)
435 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
436 {:ok, new_user} = User.ensure_keys_present(user)
439 if new_user != user and match?(%User{}, for_user) do
440 User.get_cached_by_nickname(for_user.nickname)