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 case conn.assigns[:user] do
76 %User{id: user_id} -> Delivery.create(object_id, user_id)
83 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
84 with ap_id <- o_status_url(conn, :object, uuid),
85 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
86 {_, true} <- {:public?, Visibility.is_public?(object)},
87 likes <- Utils.get_object_likes(object) do
88 {page, _} = Integer.parse(page)
91 |> put_resp_content_type("application/activity+json")
92 |> json(ObjectView.render("likes.json", ap_id, likes, page))
99 def object_likes(conn, %{"uuid" => uuid}) do
100 with ap_id <- o_status_url(conn, :object, uuid),
101 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
102 {_, true} <- {:public?, Visibility.is_public?(object)},
103 likes <- Utils.get_object_likes(object) do
105 |> put_resp_content_type("application/activity+json")
106 |> json(ObjectView.render("likes.json", ap_id, likes))
113 def activity(conn, %{"uuid" => uuid}) do
114 with ap_id <- o_status_url(conn, :activity, uuid),
115 %Activity{} = activity <- Activity.normalize(ap_id),
116 {_, true} <- {:public?, Visibility.is_public?(activity)} do
118 |> maybe_set_tracking_data(activity)
119 |> set_cache_ttl_for(activity)
120 |> put_resp_content_type("application/activity+json")
121 |> put_view(ObjectView)
122 |> render("object.json", object: activity)
124 {:public?, false} -> {:error, :not_found}
125 nil -> {:error, :not_found}
129 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
130 object_id = Object.normalize(activity).id
131 assign(conn, :tracking_fun_data, object_id)
134 defp maybe_set_tracking_data(conn, _activity), do: assign(conn, :tracking_fun_data, nil)
136 defp set_cache_ttl_for(conn, %Activity{object: object}) do
137 set_cache_ttl_for(conn, object)
140 defp set_cache_ttl_for(conn, entity) do
143 %Object{data: %{"type" => "Question"}} ->
144 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
147 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
153 assign(conn, :cache_ttl, ttl)
156 # GET /relay/following
157 def following(%{assigns: %{relay: true}} = conn, _params) do
159 |> put_resp_content_type("application/activity+json")
160 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
163 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
164 with %User{} = user <- User.get_cached_by_nickname(nickname),
165 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
166 {:show_follows, true} <-
167 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
168 {page, _} = Integer.parse(page)
171 |> put_resp_content_type("application/activity+json")
172 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
174 {:show_follows, _} ->
176 |> put_resp_content_type("application/activity+json")
177 |> send_resp(403, "")
181 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
182 with %User{} = user <- User.get_cached_by_nickname(nickname),
183 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
185 |> put_resp_content_type("application/activity+json")
186 |> json(UserView.render("following.json", %{user: user, for: for_user}))
190 # GET /relay/followers
191 def followers(%{assigns: %{relay: true}} = conn, _params) do
193 |> put_resp_content_type("application/activity+json")
194 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
197 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
198 with %User{} = user <- User.get_cached_by_nickname(nickname),
199 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
200 {:show_followers, true} <-
201 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
202 {page, _} = Integer.parse(page)
205 |> put_resp_content_type("application/activity+json")
206 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
208 {:show_followers, _} ->
210 |> put_resp_content_type("application/activity+json")
211 |> send_resp(403, "")
215 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
216 with %User{} = user <- User.get_cached_by_nickname(nickname),
217 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
219 |> put_resp_content_type("application/activity+json")
220 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
224 def outbox(conn, %{"nickname" => nickname} = params) do
225 with %User{} = user <- User.get_cached_by_nickname(nickname),
226 {:ok, user} <- User.ensure_keys_present(user) do
228 |> put_resp_content_type("application/activity+json")
229 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
233 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
234 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
235 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
236 true <- Utils.recipient_in_message(recipient, actor, params),
237 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
238 Federator.incoming_ap_doc(params)
243 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
244 Federator.incoming_ap_doc(params)
248 # only accept relayed Creates
249 def inbox(conn, %{"type" => "Create"} = params) do
251 "Signature missing or not from author, relayed Create message, fetching object from source"
254 Fetcher.fetch_object_from_id(params["object"]["id"])
259 def inbox(conn, params) do
260 headers = Enum.into(conn.req_headers, %{})
262 if String.contains?(headers["signature"], params["actor"]) do
264 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
267 Logger.info(inspect(conn.req_headers))
270 json(conn, dgettext("errors", "error"))
273 defp represent_service_actor(%User{} = user, conn) do
274 with {:ok, user} <- User.ensure_keys_present(user) do
276 |> put_resp_content_type("application/activity+json")
277 |> json(UserView.render("user.json", %{user: user}))
279 nil -> {:error, :not_found}
283 defp represent_service_actor(nil, _), do: {:error, :not_found}
285 def relay(conn, _params) do
287 |> represent_service_actor(conn)
290 def internal_fetch(conn, _params) do
291 InternalFetchActor.get_actor()
292 |> represent_service_actor(conn)
295 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
297 |> put_resp_content_type("application/activity+json")
298 |> json(UserView.render("user.json", %{user: user}))
301 def whoami(_conn, _params), do: {:error, :not_found}
304 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
305 %{"nickname" => nickname} = params
308 |> put_resp_content_type("application/activity+json")
309 |> put_view(UserView)
310 |> render("inbox.json", user: user, max_id: params["max_id"])
313 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
314 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
317 |> put_status(:forbidden)
321 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
322 "nickname" => nickname
325 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
327 as_nickname: as_nickname
331 |> put_status(:forbidden)
335 def handle_user_activity(user, %{"type" => "Create"} = params) do
338 |> Map.merge(Map.take(params, ["to", "cc"]))
339 |> Map.put("attributedTo", user.ap_id())
340 |> Transmogrifier.fix_object()
342 ActivityPub.create(%{
345 context: object["context"],
347 additional: Map.take(params, ["cc"])
351 def handle_user_activity(user, %{"type" => "Delete"} = params) do
352 with %Object{} = object <- Object.normalize(params["object"]),
353 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
354 {:ok, delete} <- ActivityPub.delete(object) do
357 _ -> {:error, dgettext("errors", "Can't delete object")}
361 def handle_user_activity(user, %{"type" => "Like"} = params) do
362 with %Object{} = object <- Object.normalize(params["object"]),
363 {:ok, activity, _object} <- ActivityPub.like(user, object) do
366 _ -> {:error, dgettext("errors", "Can't like object")}
370 def handle_user_activity(_, _) do
371 {:error, dgettext("errors", "Unhandled activity type")}
375 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
376 %{"nickname" => nickname} = params
383 |> Map.put("actor", actor)
384 |> Transmogrifier.fix_addressing()
386 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
388 |> put_status(:created)
389 |> put_resp_header("location", activity.data["id"])
390 |> json(activity.data)
394 |> put_status(:bad_request)
399 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
401 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
403 as_nickname: user.nickname
407 |> put_status(:forbidden)
411 def errors(conn, {:error, :not_found}) do
413 |> put_status(:not_found)
414 |> json(dgettext("errors", "Not found"))
417 def errors(conn, _e) do
419 |> put_status(:internal_server_error)
420 |> json(dgettext("errors", "error"))
423 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
424 with actor <- conn.params["actor"],
425 true <- is_binary(actor) do
426 Pleroma.Instances.set_reachable(actor)
432 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
433 {:ok, new_user} = User.ensure_keys_present(user)
436 if new_user != user and match?(%User{}, for_user) do
437 User.get_cached_by_nickname(for_user.nickname)