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")
52 |> json(UserView.render("user.json", %{user: user}))
54 nil -> {:error, :not_found}
58 def object(conn, %{"uuid" => uuid}) do
59 with ap_id <- o_status_url(conn, :object, uuid),
60 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
61 {_, true} <- {:public?, Visibility.is_public?(object)} do
63 |> assign(:tracking_fun_data, object.id)
64 |> set_cache_ttl_for(object)
65 |> put_resp_content_type("application/activity+json")
66 |> put_view(ObjectView)
67 |> render("object.json", object: object)
74 def track_object_fetch(conn, object_id) do
75 with %{assigns: %{user: %User{id: user_id}}} <- conn do
76 Delivery.create(object_id, user_id)
82 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
83 with ap_id <- o_status_url(conn, :object, uuid),
84 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
85 {_, true} <- {:public?, Visibility.is_public?(object)},
86 likes <- Utils.get_object_likes(object) do
87 {page, _} = Integer.parse(page)
90 |> put_resp_content_type("application/activity+json")
91 |> json(ObjectView.render("likes.json", ap_id, likes, page))
98 def object_likes(conn, %{"uuid" => uuid}) do
99 with ap_id <- o_status_url(conn, :object, uuid),
100 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
101 {_, true} <- {:public?, Visibility.is_public?(object)},
102 likes <- Utils.get_object_likes(object) do
104 |> put_resp_content_type("application/activity+json")
105 |> json(ObjectView.render("likes.json", ap_id, likes))
112 def activity(conn, %{"uuid" => uuid}) do
113 with ap_id <- o_status_url(conn, :activity, uuid),
114 %Activity{} = activity <- Activity.normalize(ap_id),
115 {_, true} <- {:public?, Visibility.is_public?(activity)} do
117 |> maybe_set_tracking_data(activity)
118 |> set_cache_ttl_for(activity)
119 |> put_resp_content_type("application/activity+json")
120 |> put_view(ObjectView)
121 |> render("object.json", object: activity)
123 {:public?, false} -> {:error, :not_found}
124 nil -> {:error, :not_found}
128 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
129 object_id = Object.normalize(activity).id
130 assign(conn, :tracking_fun_data, object_id)
133 defp maybe_set_tracking_data(conn, _activity), do: conn
135 defp set_cache_ttl_for(conn, %Activity{object: object}) do
136 set_cache_ttl_for(conn, object)
139 defp set_cache_ttl_for(conn, entity) do
142 %Object{data: %{"type" => "Question"}} ->
143 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
146 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
152 assign(conn, :cache_ttl, ttl)
155 # GET /relay/following
156 def following(%{assigns: %{relay: true}} = conn, _params) do
158 |> put_resp_content_type("application/activity+json")
159 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
162 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
163 with %User{} = user <- User.get_cached_by_nickname(nickname),
164 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
165 {:show_follows, true} <-
166 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
167 {page, _} = Integer.parse(page)
170 |> put_resp_content_type("application/activity+json")
171 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
173 {:show_follows, _} ->
175 |> put_resp_content_type("application/activity+json")
176 |> send_resp(403, "")
180 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
181 with %User{} = user <- User.get_cached_by_nickname(nickname),
182 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
184 |> put_resp_content_type("application/activity+json")
185 |> json(UserView.render("following.json", %{user: user, for: for_user}))
189 # GET /relay/followers
190 def followers(%{assigns: %{relay: true}} = conn, _params) do
192 |> put_resp_content_type("application/activity+json")
193 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
196 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
197 with %User{} = user <- User.get_cached_by_nickname(nickname),
198 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
199 {:show_followers, true} <-
200 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
201 {page, _} = Integer.parse(page)
204 |> put_resp_content_type("application/activity+json")
205 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
207 {:show_followers, _} ->
209 |> put_resp_content_type("application/activity+json")
210 |> send_resp(403, "")
214 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
215 with %User{} = user <- User.get_cached_by_nickname(nickname),
216 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
218 |> put_resp_content_type("application/activity+json")
219 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
223 def outbox(conn, %{"nickname" => nickname} = params) do
224 with %User{} = user <- User.get_cached_by_nickname(nickname),
225 {:ok, user} <- User.ensure_keys_present(user) do
227 |> put_resp_content_type("application/activity+json")
228 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
232 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
233 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
234 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
235 true <- Utils.recipient_in_message(recipient, actor, params),
236 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
237 Federator.incoming_ap_doc(params)
242 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
243 Federator.incoming_ap_doc(params)
247 # only accept relayed Creates
248 def inbox(conn, %{"type" => "Create"} = params) do
250 "Signature missing or not from author, relayed Create message, fetching object from source"
253 Fetcher.fetch_object_from_id(params["object"]["id"])
258 def inbox(conn, params) do
259 headers = Enum.into(conn.req_headers, %{})
261 if String.contains?(headers["signature"], params["actor"]) do
263 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
266 Logger.info(inspect(conn.req_headers))
269 json(conn, dgettext("errors", "error"))
272 defp represent_service_actor(%User{} = user, conn) do
273 with {:ok, user} <- User.ensure_keys_present(user) do
275 |> put_resp_content_type("application/activity+json")
276 |> json(UserView.render("user.json", %{user: user}))
278 nil -> {:error, :not_found}
282 defp represent_service_actor(nil, _), do: {:error, :not_found}
284 def relay(conn, _params) do
286 |> represent_service_actor(conn)
289 def internal_fetch(conn, _params) do
290 InternalFetchActor.get_actor()
291 |> represent_service_actor(conn)
294 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
296 |> put_resp_content_type("application/activity+json")
297 |> json(UserView.render("user.json", %{user: user}))
300 def whoami(_conn, _params), do: {:error, :not_found}
303 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
304 %{"nickname" => nickname} = params
307 |> put_resp_content_type("application/activity+json")
308 |> put_view(UserView)
309 |> render("inbox.json", user: user, max_id: params["max_id"])
312 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
313 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
316 |> put_status(:forbidden)
320 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
321 "nickname" => nickname
324 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
326 as_nickname: as_nickname
330 |> put_status(:forbidden)
334 def handle_user_activity(user, %{"type" => "Create"} = params) do
337 |> Map.merge(Map.take(params, ["to", "cc"]))
338 |> Map.put("attributedTo", user.ap_id())
339 |> Transmogrifier.fix_object()
341 ActivityPub.create(%{
344 context: object["context"],
346 additional: Map.take(params, ["cc"])
350 def handle_user_activity(user, %{"type" => "Delete"} = params) do
351 with %Object{} = object <- Object.normalize(params["object"]),
352 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
353 {:ok, delete} <- ActivityPub.delete(object) do
356 _ -> {:error, dgettext("errors", "Can't delete object")}
360 def handle_user_activity(user, %{"type" => "Like"} = params) do
361 with %Object{} = object <- Object.normalize(params["object"]),
362 {:ok, activity, _object} <- ActivityPub.like(user, object) do
365 _ -> {:error, dgettext("errors", "Can't like object")}
369 def handle_user_activity(_, _) do
370 {:error, dgettext("errors", "Unhandled activity type")}
374 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
375 %{"nickname" => nickname} = params
382 |> Map.put("actor", actor)
383 |> Transmogrifier.fix_addressing()
385 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
387 |> put_status(:created)
388 |> put_resp_header("location", activity.data["id"])
389 |> json(activity.data)
393 |> put_status(:bad_request)
398 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
400 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
402 as_nickname: user.nickname
406 |> put_status(:forbidden)
410 def errors(conn, {:error, :not_found}) do
412 |> put_status(:not_found)
413 |> json(dgettext("errors", "Not found"))
416 def errors(conn, _e) do
418 |> put_status(:internal_server_error)
419 |> json(dgettext("errors", "error"))
422 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
423 with actor <- conn.params["actor"],
424 true <- is_binary(actor) do
425 Pleroma.Instances.set_reachable(actor)
431 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
432 {:ok, new_user} = User.ensure_keys_present(user)
435 if new_user != user and match?(%User{}, for_user) do
436 User.get_cached_by_nickname(for_user.nickname)