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) do
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
209 # 'Answer', 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
221 |> put_resp_content_type("application/activity+json")
222 |> put_view(UserView)
223 |> render("activity_collection_page.json", %{
224 activities: activities,
225 iri: "#{user.ap_id}/outbox"
230 def outbox(conn, %{"nickname" => nickname}) do
231 with %User{} = user <- User.get_cached_by_nickname(nickname),
232 {:ok, user} <- User.ensure_keys_present(user) do
234 |> put_resp_content_type("application/activity+json")
235 |> put_view(UserView)
236 |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
240 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
241 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
242 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
243 true <- Utils.recipient_in_message(recipient, actor, params),
244 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
245 Federator.incoming_ap_doc(params)
250 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
251 Federator.incoming_ap_doc(params)
255 # only accept relayed Creates
256 def inbox(conn, %{"type" => "Create"} = params) do
258 "Signature missing or not from author, relayed Create message, fetching object from source"
261 Fetcher.fetch_object_from_id(params["object"]["id"])
266 def inbox(conn, params) do
267 headers = Enum.into(conn.req_headers, %{})
269 if String.contains?(headers["signature"], params["actor"]) do
271 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
274 Logger.info(inspect(conn.req_headers))
277 json(conn, dgettext("errors", "error"))
280 defp represent_service_actor(%User{} = user, conn) do
281 with {:ok, user} <- User.ensure_keys_present(user) do
283 |> put_resp_content_type("application/activity+json")
284 |> json(UserView.render("user.json", %{user: user}))
286 nil -> {:error, :not_found}
290 defp represent_service_actor(nil, _), do: {:error, :not_found}
292 def relay(conn, _params) do
294 |> represent_service_actor(conn)
297 def internal_fetch(conn, _params) do
298 InternalFetchActor.get_actor()
299 |> represent_service_actor(conn)
302 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
304 |> put_resp_content_type("application/activity+json")
305 |> json(UserView.render("user.json", %{user: user}))
308 def whoami(_conn, _params), do: {:error, :not_found}
311 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
312 %{"nickname" => nickname, "page" => page?} = params
314 when page? in [true, "true"] do
316 if params["max_id"] do
317 ActivityPub.fetch_activities([user.ap_id | user.following], %{
318 "max_id" => params["max_id"],
322 ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
326 |> put_resp_content_type("application/activity+json")
327 |> put_view(UserView)
328 |> render("activity_collection_page.json", %{
329 activities: activities,
330 iri: "#{user.ap_id}/inbox"
334 def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
335 "nickname" => nickname
337 with {:ok, user} <- User.ensure_keys_present(user) do
339 |> put_resp_content_type("application/activity+json")
340 |> put_view(UserView)
341 |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
345 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
346 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
349 |> put_status(:forbidden)
353 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
354 "nickname" => nickname
357 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
359 as_nickname: as_nickname
363 |> put_status(:forbidden)
367 def handle_user_activity(user, %{"type" => "Create"} = params) do
370 |> Map.merge(Map.take(params, ["to", "cc"]))
371 |> Map.put("attributedTo", user.ap_id())
372 |> Transmogrifier.fix_object()
374 ActivityPub.create(%{
377 context: object["context"],
379 additional: Map.take(params, ["cc"])
383 def handle_user_activity(user, %{"type" => "Delete"} = params) do
384 with %Object{} = object <- Object.normalize(params["object"]),
385 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
386 {:ok, delete} <- ActivityPub.delete(object) do
389 _ -> {:error, dgettext("errors", "Can't delete object")}
393 def handle_user_activity(user, %{"type" => "Like"} = params) do
394 with %Object{} = object <- Object.normalize(params["object"]),
395 {:ok, activity, _object} <- ActivityPub.like(user, object) do
398 _ -> {:error, dgettext("errors", "Can't like object")}
402 def handle_user_activity(_, _) do
403 {:error, dgettext("errors", "Unhandled activity type")}
407 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
408 %{"nickname" => nickname} = params
415 |> Map.put("actor", actor)
416 |> Transmogrifier.fix_addressing()
418 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
420 |> put_status(:created)
421 |> put_resp_header("location", activity.data["id"])
422 |> json(activity.data)
426 |> put_status(:bad_request)
431 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
433 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
435 as_nickname: user.nickname
439 |> put_status(:forbidden)
443 def errors(conn, {:error, :not_found}) do
445 |> put_status(:not_found)
446 |> json(dgettext("errors", "Not found"))
449 def errors(conn, _e) do
451 |> put_status(:internal_server_error)
452 |> json(dgettext("errors", "error"))
455 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
456 with actor <- conn.params["actor"],
457 true <- is_binary(actor) do
458 Pleroma.Instances.set_reachable(actor)
464 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
465 {:ok, new_user} = User.ensure_keys_present(user)
468 if new_user != user and match?(%User{}, for_user) do
469 User.get_cached_by_nickname(for_user.nickname)