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)
29 [query_params: false, tracking_fun: &__MODULE__.track_object_fetch/2]
30 when action in [:activity, :object]
33 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
34 plug(:set_requester_reachable when action in [:inbox])
35 plug(:relay_active? when action in [:relay])
37 def relay_active?(conn, _) do
38 if Pleroma.Config.get([:instance, :allow_relay]) do
42 |> render_error(:not_found, "not found")
47 def user(conn, %{"nickname" => nickname}) do
48 with %User{} = user <- User.get_cached_by_nickname(nickname),
49 {:ok, user} <- User.ensure_keys_present(user) do
51 |> put_resp_content_type("application/activity+json")
53 |> render("user.json", %{user: user})
55 nil -> {:error, :not_found}
59 def object(conn, %{"uuid" => uuid}) do
60 with ap_id <- o_status_url(conn, :object, uuid),
61 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
62 {_, true} <- {:public?, Visibility.is_public?(object)} do
64 |> assign(:tracking_fun_data, object.id)
65 |> set_cache_ttl_for(object)
66 |> put_resp_content_type("application/activity+json")
67 |> put_view(ObjectView)
68 |> render("object.json", object: object)
75 def track_object_fetch(conn, nil), do: conn
77 def track_object_fetch(conn, object_id) do
78 with %{assigns: %{user: %User{id: user_id}}} <- conn do
79 Delivery.create(object_id, user_id)
85 def object_likes(conn, %{"uuid" => uuid, "page" => page}) 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
90 {page, _} = Integer.parse(page)
93 |> put_resp_content_type("application/activity+json")
94 |> put_view(ObjectView)
95 |> render("likes.json", %{ap_id: ap_id, likes: likes, page: 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 |> put_view(ObjectView)
110 |> render("likes.json", %{ap_id: ap_id, likes: likes})
117 def activity(conn, %{"uuid" => uuid}) do
118 with ap_id <- o_status_url(conn, :activity, uuid),
119 %Activity{} = activity <- Activity.normalize(ap_id),
120 {_, true} <- {:public?, Visibility.is_public?(activity)} do
122 |> maybe_set_tracking_data(activity)
123 |> set_cache_ttl_for(activity)
124 |> put_resp_content_type("application/activity+json")
125 |> put_view(ObjectView)
126 |> render("object.json", object: activity)
128 {:public?, false} -> {:error, :not_found}
129 nil -> {:error, :not_found}
133 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
134 object_id = Object.normalize(activity).id
135 assign(conn, :tracking_fun_data, object_id)
138 defp maybe_set_tracking_data(conn, _activity), do: conn
140 defp set_cache_ttl_for(conn, %Activity{object: object}) do
141 set_cache_ttl_for(conn, object)
144 defp set_cache_ttl_for(conn, entity) do
147 %Object{data: %{"type" => "Question"}} ->
148 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
151 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
157 assign(conn, :cache_ttl, ttl)
160 # GET /relay/following
161 def following(%{assigns: %{relay: true}} = conn, _params) do
163 |> put_resp_content_type("application/activity+json")
164 |> put_view(UserView)
165 |> render("following.json", %{user: Relay.get_actor()})
168 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
169 with %User{} = user <- User.get_cached_by_nickname(nickname),
170 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
171 {:show_follows, true} <-
172 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
173 {page, _} = Integer.parse(page)
176 |> put_resp_content_type("application/activity+json")
177 |> put_view(UserView)
178 |> render("following.json", %{user: user, page: page, for: for_user})
180 {:show_follows, _} ->
182 |> put_resp_content_type("application/activity+json")
183 |> send_resp(403, "")
187 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
188 with %User{} = user <- User.get_cached_by_nickname(nickname),
189 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
191 |> put_resp_content_type("application/activity+json")
192 |> put_view(UserView)
193 |> render("following.json", %{user: user, for: for_user})
197 # GET /relay/followers
198 def followers(%{assigns: %{relay: true}} = conn, _params) do
200 |> put_resp_content_type("application/activity+json")
201 |> put_view(UserView)
202 |> render("followers.json", %{user: Relay.get_actor()})
205 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
206 with %User{} = user <- User.get_cached_by_nickname(nickname),
207 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
208 {:show_followers, true} <-
209 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
210 {page, _} = Integer.parse(page)
213 |> put_resp_content_type("application/activity+json")
214 |> put_view(UserView)
215 |> render("followers.json", %{user: user, page: page, for: for_user})
217 {:show_followers, _} ->
219 |> put_resp_content_type("application/activity+json")
220 |> send_resp(403, "")
224 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
225 with %User{} = user <- User.get_cached_by_nickname(nickname),
226 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
228 |> put_resp_content_type("application/activity+json")
229 |> put_view(UserView)
230 |> render("followers.json", %{user: user, for: for_user})
234 def outbox(conn, %{"nickname" => nickname} = params) do
235 with %User{} = user <- User.get_cached_by_nickname(nickname),
236 {:ok, user} <- User.ensure_keys_present(user) do
238 |> put_resp_content_type("application/activity+json")
239 |> put_view(UserView)
240 |> render("outbox.json", %{user: user, max_id: params["max_id"]})
244 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
245 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
246 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
247 true <- Utils.recipient_in_message(recipient, actor, params),
248 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
249 Federator.incoming_ap_doc(params)
254 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
255 Federator.incoming_ap_doc(params)
259 # only accept relayed Creates
260 def inbox(conn, %{"type" => "Create"} = params) do
262 "Signature missing or not from author, relayed Create message, fetching object from source"
265 Fetcher.fetch_object_from_id(params["object"]["id"])
270 def inbox(conn, params) do
271 headers = Enum.into(conn.req_headers, %{})
273 if String.contains?(headers["signature"], params["actor"]) do
275 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
278 Logger.info(inspect(conn.req_headers))
281 json(conn, dgettext("errors", "error"))
284 defp represent_service_actor(%User{} = user, conn) do
285 with {:ok, user} <- User.ensure_keys_present(user) do
287 |> put_resp_content_type("application/activity+json")
288 |> put_view(UserView)
289 |> render("user.json", %{user: user})
291 nil -> {:error, :not_found}
295 defp represent_service_actor(nil, _), do: {:error, :not_found}
297 def relay(conn, _params) do
299 |> represent_service_actor(conn)
302 def internal_fetch(conn, _params) do
303 InternalFetchActor.get_actor()
304 |> represent_service_actor(conn)
307 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
309 |> put_resp_content_type("application/activity+json")
310 |> put_view(UserView)
311 |> render("user.json", %{user: user})
314 def whoami(_conn, _params), do: {:error, :not_found}
317 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
318 %{"nickname" => nickname} = params
321 |> put_resp_content_type("application/activity+json")
322 |> put_view(UserView)
323 |> render("inbox.json", user: user, max_id: params["max_id"])
326 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
327 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
330 |> put_status(:forbidden)
334 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
335 "nickname" => nickname
338 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
340 as_nickname: as_nickname
344 |> put_status(:forbidden)
348 def handle_user_activity(user, %{"type" => "Create"} = params) do
351 |> Map.merge(Map.take(params, ["to", "cc"]))
352 |> Map.put("attributedTo", user.ap_id())
353 |> Transmogrifier.fix_object()
355 ActivityPub.create(%{
358 context: object["context"],
360 additional: Map.take(params, ["cc"])
364 def handle_user_activity(user, %{"type" => "Delete"} = params) do
365 with %Object{} = object <- Object.normalize(params["object"]),
366 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
367 {:ok, delete} <- ActivityPub.delete(object) do
370 _ -> {:error, dgettext("errors", "Can't delete object")}
374 def handle_user_activity(user, %{"type" => "Like"} = params) do
375 with %Object{} = object <- Object.normalize(params["object"]),
376 {:ok, activity, _object} <- ActivityPub.like(user, object) do
379 _ -> {:error, dgettext("errors", "Can't like object")}
383 def handle_user_activity(_, _) do
384 {:error, dgettext("errors", "Unhandled activity type")}
388 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
389 %{"nickname" => nickname} = params
396 |> Map.put("actor", actor)
397 |> Transmogrifier.fix_addressing()
399 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
401 |> put_status(:created)
402 |> put_resp_header("location", activity.data["id"])
403 |> json(activity.data)
407 |> put_status(:bad_request)
412 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
414 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
416 as_nickname: user.nickname
420 |> put_status(:forbidden)
424 def errors(conn, {:error, :not_found}) do
426 |> put_status(:not_found)
427 |> json(dgettext("errors", "Not found"))
430 def errors(conn, _e) do
432 |> put_status(:internal_server_error)
433 |> json(dgettext("errors", "error"))
436 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
437 with actor <- conn.params["actor"],
438 true <- is_binary(actor) do
439 Pleroma.Instances.set_reachable(actor)
445 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
446 {:ok, new_user} = User.ensure_keys_present(user)
449 if new_user != user and match?(%User{}, for_user) do
450 User.get_cached_by_nickname(for_user.nickname)