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)
26 plug(Pleroma.Plugs.Cache, [query_params: false] when action in [:activity, :object])
27 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
28 plug(:set_requester_reachable when action in [:inbox])
29 plug(:relay_active? when action in [:relay])
31 def relay_active?(conn, _) do
32 if Pleroma.Config.get([:instance, :allow_relay]) do
36 |> render_error(:not_found, "not found")
41 def user(conn, %{"nickname" => nickname}) do
42 with %User{} = user <- User.get_cached_by_nickname(nickname),
43 {:ok, user} <- User.ensure_keys_present(user) do
45 |> put_resp_content_type("application/activity+json")
46 |> json(UserView.render("user.json", %{user: user}))
48 nil -> {:error, :not_found}
52 def object(conn, %{"uuid" => uuid}) do
53 with ap_id <- o_status_url(conn, :object, uuid),
54 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
55 {_, true} <- {:public?, Visibility.is_public?(object)} do
57 |> set_cache_ttl_for(object)
58 |> put_resp_content_type("application/activity+json")
59 |> put_view(ObjectView)
60 |> render("object.json", object: object)
67 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
68 with ap_id <- o_status_url(conn, :object, uuid),
69 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
70 {_, true} <- {:public?, Visibility.is_public?(object)},
71 likes <- Utils.get_object_likes(object) do
72 {page, _} = Integer.parse(page)
75 |> put_resp_content_type("application/activity+json")
76 |> json(ObjectView.render("likes.json", ap_id, likes, page))
83 def object_likes(conn, %{"uuid" => uuid}) 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
89 |> put_resp_content_type("application/activity+json")
90 |> json(ObjectView.render("likes.json", ap_id, likes))
97 def activity(conn, %{"uuid" => uuid}) do
98 with ap_id <- o_status_url(conn, :activity, uuid),
99 %Activity{} = activity <- Activity.normalize(ap_id),
100 {_, true} <- {:public?, Visibility.is_public?(activity)} do
102 |> set_cache_ttl_for(activity)
103 |> put_resp_content_type("application/activity+json")
104 |> put_view(ObjectView)
105 |> render("object.json", object: activity)
107 {:public?, false} -> {:error, :not_found}
108 nil -> {:error, :not_found}
112 defp set_cache_ttl_for(conn, %Activity{object: object}) do
113 set_cache_ttl_for(conn, object)
116 defp set_cache_ttl_for(conn, entity) do
119 %Object{data: %{"type" => "Question"}} ->
120 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
123 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
129 assign(conn, :cache_ttl, ttl)
132 # GET /relay/following
133 def following(%{assigns: %{relay: true}} = conn, _params) do
135 |> put_resp_content_type("application/activity+json")
136 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
139 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
140 with %User{} = user <- User.get_cached_by_nickname(nickname),
141 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
142 {:show_follows, true} <-
143 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
144 {page, _} = Integer.parse(page)
147 |> put_resp_content_type("application/activity+json")
148 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
150 {:show_follows, _} ->
152 |> put_resp_content_type("application/activity+json")
153 |> send_resp(403, "")
157 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
158 with %User{} = user <- User.get_cached_by_nickname(nickname),
159 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
161 |> put_resp_content_type("application/activity+json")
162 |> json(UserView.render("following.json", %{user: user, for: for_user}))
166 # GET /relay/followers
167 def followers(%{assigns: %{relay: true}} = conn, _params) do
169 |> put_resp_content_type("application/activity+json")
170 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
173 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
174 with %User{} = user <- User.get_cached_by_nickname(nickname),
175 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
176 {:show_followers, true} <-
177 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
178 {page, _} = Integer.parse(page)
181 |> put_resp_content_type("application/activity+json")
182 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
184 {:show_followers, _} ->
186 |> put_resp_content_type("application/activity+json")
187 |> send_resp(403, "")
191 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
192 with %User{} = user <- User.get_cached_by_nickname(nickname),
193 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
195 |> put_resp_content_type("application/activity+json")
196 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
200 def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
201 when page? in [true, "true"] do
202 with %User{} = user <- User.get_cached_by_nickname(nickname),
203 {:ok, user} <- User.ensure_keys_present(user),
205 (if params["max_id"] do
206 ActivityPub.fetch_user_activities(user, nil, %{
207 "max_id" => params["max_id"],
208 # This is a hack because postgres generates inefficient queries when filtering by 'Answer',
209 # poll votes will be hidden by the visibility filter in this case anyway
210 "include_poll_votes" => true,
214 ActivityPub.fetch_user_activities(user, nil, %{
216 "include_poll_votes" => true
220 |> put_resp_content_type("application/activity+json")
221 |> put_view(UserView)
222 |> render("activity_collection_page.json", %{
223 activities: activities,
224 iri: "#{user.ap_id}/outbox"
229 def outbox(conn, %{"nickname" => nickname}) do
230 with %User{} = user <- User.get_cached_by_nickname(nickname),
231 {:ok, user} <- User.ensure_keys_present(user) do
233 |> put_resp_content_type("application/activity+json")
234 |> put_view(UserView)
235 |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
239 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
240 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
241 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
242 true <- Utils.recipient_in_message(recipient, actor, params),
243 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
244 Federator.incoming_ap_doc(params)
249 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
250 Federator.incoming_ap_doc(params)
254 # only accept relayed Creates
255 def inbox(conn, %{"type" => "Create"} = params) do
257 "Signature missing or not from author, relayed Create message, fetching object from source"
260 Fetcher.fetch_object_from_id(params["object"]["id"])
265 def inbox(conn, params) do
266 headers = Enum.into(conn.req_headers, %{})
268 if String.contains?(headers["signature"], params["actor"]) do
270 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
273 Logger.info(inspect(conn.req_headers))
276 json(conn, dgettext("errors", "error"))
279 defp represent_service_actor(%User{} = user, conn) do
280 with {:ok, user} <- User.ensure_keys_present(user) do
282 |> put_resp_content_type("application/activity+json")
283 |> json(UserView.render("user.json", %{user: user}))
285 nil -> {:error, :not_found}
289 defp represent_service_actor(nil, _), do: {:error, :not_found}
291 def relay(conn, _params) do
293 |> represent_service_actor(conn)
296 def internal_fetch(conn, _params) do
297 InternalFetchActor.get_actor()
298 |> represent_service_actor(conn)
301 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
303 |> put_resp_content_type("application/activity+json")
304 |> json(UserView.render("user.json", %{user: user}))
307 def whoami(_conn, _params), do: {:error, :not_found}
310 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
311 %{"nickname" => nickname, "page" => page?} = params
313 when page? in [true, "true"] do
315 (if params["max_id"] do
316 ActivityPub.fetch_activities([user.ap_id | user.following], %{
317 "max_id" => params["max_id"],
321 ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
324 |> put_resp_content_type("application/activity+json")
325 |> put_view(UserView)
326 |> render("activity_collection_page.json", %{
327 activities: activities,
328 iri: "#{user.ap_id}/inbox"
333 def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
334 "nickname" => nickname
336 with {:ok, user} <- User.ensure_keys_present(user) do
338 |> put_resp_content_type("application/activity+json")
339 |> put_view(UserView)
340 |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
344 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
345 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
348 |> put_status(:forbidden)
352 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
353 "nickname" => nickname
356 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
358 as_nickname: as_nickname
362 |> put_status(:forbidden)
366 def handle_user_activity(user, %{"type" => "Create"} = params) do
369 |> Map.merge(Map.take(params, ["to", "cc"]))
370 |> Map.put("attributedTo", user.ap_id())
371 |> Transmogrifier.fix_object()
373 ActivityPub.create(%{
376 context: object["context"],
378 additional: Map.take(params, ["cc"])
382 def handle_user_activity(user, %{"type" => "Delete"} = params) do
383 with %Object{} = object <- Object.normalize(params["object"]),
384 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
385 {:ok, delete} <- ActivityPub.delete(object) do
388 _ -> {:error, dgettext("errors", "Can't delete object")}
392 def handle_user_activity(user, %{"type" => "Like"} = params) do
393 with %Object{} = object <- Object.normalize(params["object"]),
394 {:ok, activity, _object} <- ActivityPub.like(user, object) do
397 _ -> {:error, dgettext("errors", "Can't like object")}
401 def handle_user_activity(_, _) do
402 {:error, dgettext("errors", "Unhandled activity type")}
406 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
407 %{"nickname" => nickname} = params
414 |> Map.put("actor", actor)
415 |> Transmogrifier.fix_addressing()
417 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
419 |> put_status(:created)
420 |> put_resp_header("location", activity.data["id"])
421 |> json(activity.data)
425 |> put_status(:bad_request)
430 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
432 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
434 as_nickname: user.nickname
438 |> put_status(:forbidden)
442 def errors(conn, {:error, :not_found}) do
444 |> put_status(:not_found)
445 |> json(dgettext("errors", "Not found"))
448 def errors(conn, _e) do
450 |> put_status(:internal_server_error)
451 |> json(dgettext("errors", "error"))
454 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
455 with actor <- conn.params["actor"],
456 true <- is_binary(actor) do
457 Pleroma.Instances.set_reachable(actor)
463 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
464 {:ok, new_user} = User.ensure_keys_present(user)
467 if new_user != user and match?(%User{}, for_user) do
468 User.get_cached_by_nickname(for_user.nickname)