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, "page" => page?} = params)
235 when page? in [true, "true"] do
236 with %User{} = user <- User.get_cached_by_nickname(nickname),
237 {:ok, user} <- User.ensure_keys_present(user) do
239 if params["max_id"] do
240 ActivityPub.fetch_user_activities(user, nil, %{
241 "max_id" => params["max_id"],
242 # This is a hack because postgres generates inefficient queries when filtering by
243 # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
244 "include_poll_votes" => true,
248 ActivityPub.fetch_user_activities(user, nil, %{
250 "include_poll_votes" => true
255 |> put_resp_content_type("application/activity+json")
256 |> put_view(UserView)
257 |> render("activity_collection_page.json", %{
258 activities: activities,
259 iri: "#{user.ap_id}/outbox"
264 def outbox(conn, %{"nickname" => nickname}) do
265 with %User{} = user <- User.get_cached_by_nickname(nickname),
266 {:ok, user} <- User.ensure_keys_present(user) do
268 |> put_resp_content_type("application/activity+json")
269 |> put_view(UserView)
270 |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
274 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
275 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
276 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
277 true <- Utils.recipient_in_message(recipient, actor, params),
278 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
279 Federator.incoming_ap_doc(params)
284 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
285 Federator.incoming_ap_doc(params)
289 # only accept relayed Creates
290 def inbox(conn, %{"type" => "Create"} = params) do
292 "Signature missing or not from author, relayed Create message, fetching object from source"
295 Fetcher.fetch_object_from_id(params["object"]["id"])
300 def inbox(conn, params) do
301 headers = Enum.into(conn.req_headers, %{})
303 if String.contains?(headers["signature"], params["actor"]) do
305 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
308 Logger.info(inspect(conn.req_headers))
311 json(conn, dgettext("errors", "error"))
314 defp represent_service_actor(%User{} = user, conn) do
315 with {:ok, user} <- User.ensure_keys_present(user) do
317 |> put_resp_content_type("application/activity+json")
318 |> put_view(UserView)
319 |> render("user.json", %{user: user})
321 nil -> {:error, :not_found}
325 defp represent_service_actor(nil, _), do: {:error, :not_found}
327 def relay(conn, _params) do
329 |> represent_service_actor(conn)
332 def internal_fetch(conn, _params) do
333 InternalFetchActor.get_actor()
334 |> represent_service_actor(conn)
337 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
339 |> put_resp_content_type("application/activity+json")
340 |> put_view(UserView)
341 |> render("user.json", %{user: user})
344 def whoami(_conn, _params), do: {:error, :not_found}
347 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
348 %{"nickname" => nickname, "page" => page?} = params
350 when page? in [true, "true"] do
352 if params["max_id"] do
353 ActivityPub.fetch_activities([user.ap_id | user.following], %{
354 "max_id" => params["max_id"],
358 ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
362 |> put_resp_content_type("application/activity+json")
363 |> put_view(UserView)
364 |> render("activity_collection_page.json", %{
365 activities: activities,
366 iri: "#{user.ap_id}/inbox"
370 def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
371 "nickname" => nickname
373 with {:ok, user} <- User.ensure_keys_present(user) do
375 |> put_resp_content_type("application/activity+json")
376 |> put_view(UserView)
377 |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
381 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
382 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
385 |> put_status(:forbidden)
389 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
390 "nickname" => nickname
393 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
395 as_nickname: as_nickname
399 |> put_status(:forbidden)
403 def handle_user_activity(user, %{"type" => "Create"} = params) do
406 |> Map.merge(Map.take(params, ["to", "cc"]))
407 |> Map.put("attributedTo", user.ap_id())
408 |> Transmogrifier.fix_object()
410 ActivityPub.create(%{
413 context: object["context"],
415 additional: Map.take(params, ["cc"])
419 def handle_user_activity(user, %{"type" => "Delete"} = params) do
420 with %Object{} = object <- Object.normalize(params["object"]),
421 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
422 {:ok, delete} <- ActivityPub.delete(object) do
425 _ -> {:error, dgettext("errors", "Can't delete object")}
429 def handle_user_activity(user, %{"type" => "Like"} = params) do
430 with %Object{} = object <- Object.normalize(params["object"]),
431 {:ok, activity, _object} <- ActivityPub.like(user, object) do
434 _ -> {:error, dgettext("errors", "Can't like object")}
438 def handle_user_activity(_, _) do
439 {:error, dgettext("errors", "Unhandled activity type")}
443 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
444 %{"nickname" => nickname} = params
451 |> Map.put("actor", actor)
452 |> Transmogrifier.fix_addressing()
454 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
456 |> put_status(:created)
457 |> put_resp_header("location", activity.data["id"])
458 |> json(activity.data)
462 |> put_status(:bad_request)
467 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
469 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
471 as_nickname: user.nickname
475 |> put_status(:forbidden)
479 def errors(conn, {:error, :not_found}) do
481 |> put_status(:not_found)
482 |> json(dgettext("errors", "Not found"))
485 def errors(conn, _e) do
487 |> put_status(:internal_server_error)
488 |> json(dgettext("errors", "error"))
491 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
492 with actor <- conn.params["actor"],
493 true <- is_binary(actor) do
494 Pleroma.Instances.set_reachable(actor)
500 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
501 {:ok, new_user} = User.ensure_keys_present(user)
504 if new_user != user and match?(%User{}, for_user) do
505 User.get_cached_by_nickname(for_user.nickname)