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 activity(conn, %{"uuid" => uuid}) do
68 with ap_id <- o_status_url(conn, :activity, uuid),
69 %Activity{} = activity <- Activity.normalize(ap_id),
70 {_, true} <- {:public?, Visibility.is_public?(activity)} do
72 |> set_cache_ttl_for(activity)
73 |> put_resp_content_type("application/activity+json")
74 |> put_view(ObjectView)
75 |> render("object.json", object: activity)
77 {:public?, false} -> {:error, :not_found}
78 nil -> {:error, :not_found}
82 defp set_cache_ttl_for(conn, %Activity{object: object}) do
83 set_cache_ttl_for(conn, object)
86 defp set_cache_ttl_for(conn, entity) do
89 %Object{data: %{"type" => "Question"}} ->
90 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
93 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
99 assign(conn, :cache_ttl, ttl)
102 # GET /relay/following
103 def following(%{assigns: %{relay: true}} = conn, _params) do
105 |> put_resp_content_type("application/activity+json")
106 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
109 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
110 with %User{} = user <- User.get_cached_by_nickname(nickname),
111 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
112 {:show_follows, true} <-
113 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
114 {page, _} = Integer.parse(page)
117 |> put_resp_content_type("application/activity+json")
118 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
120 {:show_follows, _} ->
122 |> put_resp_content_type("application/activity+json")
123 |> send_resp(403, "")
127 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
128 with %User{} = user <- User.get_cached_by_nickname(nickname),
129 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
131 |> put_resp_content_type("application/activity+json")
132 |> json(UserView.render("following.json", %{user: user, for: for_user}))
136 # GET /relay/followers
137 def followers(%{assigns: %{relay: true}} = conn, _params) do
139 |> put_resp_content_type("application/activity+json")
140 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
143 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
144 with %User{} = user <- User.get_cached_by_nickname(nickname),
145 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
146 {:show_followers, true} <-
147 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
148 {page, _} = Integer.parse(page)
151 |> put_resp_content_type("application/activity+json")
152 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
154 {:show_followers, _} ->
156 |> put_resp_content_type("application/activity+json")
157 |> send_resp(403, "")
161 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
162 with %User{} = user <- User.get_cached_by_nickname(nickname),
163 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
165 |> put_resp_content_type("application/activity+json")
166 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
170 def outbox(conn, %{"nickname" => nickname, "page" => page?} = params)
171 when page? in [true, "true"] do
172 with %User{} = user <- User.get_cached_by_nickname(nickname),
173 {:ok, user} <- User.ensure_keys_present(user) do
175 if params["max_id"] do
176 ActivityPub.fetch_user_activities(user, nil, %{
177 "max_id" => params["max_id"],
178 # This is a hack because postgres generates inefficient queries when filtering by
179 # 'Answer', poll votes will be hidden by the visibility filter in this case anyway
180 "include_poll_votes" => true,
184 ActivityPub.fetch_user_activities(user, nil, %{
186 "include_poll_votes" => true
191 |> put_resp_content_type("application/activity+json")
192 |> put_view(UserView)
193 |> render("activity_collection_page.json", %{
194 activities: activities,
195 iri: "#{user.ap_id}/outbox"
200 def outbox(conn, %{"nickname" => nickname}) do
201 with %User{} = user <- User.get_cached_by_nickname(nickname),
202 {:ok, user} <- User.ensure_keys_present(user) do
204 |> put_resp_content_type("application/activity+json")
205 |> put_view(UserView)
206 |> render("activity_collection.json", %{iri: "#{user.ap_id}/outbox"})
210 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
211 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
212 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
213 true <- Utils.recipient_in_message(recipient, actor, params),
214 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
215 Federator.incoming_ap_doc(params)
220 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
221 Federator.incoming_ap_doc(params)
225 # only accept relayed Creates
226 def inbox(conn, %{"type" => "Create"} = params) do
228 "Signature missing or not from author, relayed Create message, fetching object from source"
231 Fetcher.fetch_object_from_id(params["object"]["id"])
236 def inbox(conn, params) do
237 headers = Enum.into(conn.req_headers, %{})
239 if String.contains?(headers["signature"], params["actor"]) do
241 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
244 Logger.info(inspect(conn.req_headers))
247 json(conn, dgettext("errors", "error"))
250 defp represent_service_actor(%User{} = user, conn) do
251 with {:ok, user} <- User.ensure_keys_present(user) do
253 |> put_resp_content_type("application/activity+json")
254 |> json(UserView.render("user.json", %{user: user}))
256 nil -> {:error, :not_found}
260 defp represent_service_actor(nil, _), do: {:error, :not_found}
262 def relay(conn, _params) do
264 |> represent_service_actor(conn)
267 def internal_fetch(conn, _params) do
268 InternalFetchActor.get_actor()
269 |> represent_service_actor(conn)
272 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
274 |> put_resp_content_type("application/activity+json")
275 |> json(UserView.render("user.json", %{user: user}))
278 def whoami(_conn, _params), do: {:error, :not_found}
281 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
282 %{"nickname" => nickname, "page" => page?} = params
284 when page? in [true, "true"] do
286 if params["max_id"] do
287 ActivityPub.fetch_activities([user.ap_id | user.following], %{
288 "max_id" => params["max_id"],
292 ActivityPub.fetch_activities([user.ap_id | user.following], %{"limit" => 10})
296 |> put_resp_content_type("application/activity+json")
297 |> put_view(UserView)
298 |> render("activity_collection_page.json", %{
299 activities: activities,
300 iri: "#{user.ap_id}/inbox"
304 def read_inbox(%{assigns: %{user: %{nickname: nickname} = user}} = conn, %{
305 "nickname" => nickname
307 with {:ok, user} <- User.ensure_keys_present(user) do
309 |> put_resp_content_type("application/activity+json")
310 |> put_view(UserView)
311 |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"})
315 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
316 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
319 |> put_status(:forbidden)
323 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
324 "nickname" => nickname
327 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
329 as_nickname: as_nickname
333 |> put_status(:forbidden)
337 def handle_user_activity(user, %{"type" => "Create"} = params) do
340 |> Map.merge(Map.take(params, ["to", "cc"]))
341 |> Map.put("attributedTo", user.ap_id())
342 |> Transmogrifier.fix_object()
344 ActivityPub.create(%{
347 context: object["context"],
349 additional: Map.take(params, ["cc"])
353 def handle_user_activity(user, %{"type" => "Delete"} = params) do
354 with %Object{} = object <- Object.normalize(params["object"]),
355 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
356 {:ok, delete} <- ActivityPub.delete(object) do
359 _ -> {:error, dgettext("errors", "Can't delete object")}
363 def handle_user_activity(user, %{"type" => "Like"} = params) do
364 with %Object{} = object <- Object.normalize(params["object"]),
365 {:ok, activity, _object} <- ActivityPub.like(user, object) do
368 _ -> {:error, dgettext("errors", "Can't like object")}
372 def handle_user_activity(_, _) do
373 {:error, dgettext("errors", "Unhandled activity type")}
377 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
378 %{"nickname" => nickname} = params
385 |> Map.put("actor", actor)
386 |> Transmogrifier.fix_addressing()
388 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
390 |> put_status(:created)
391 |> put_resp_header("location", activity.data["id"])
392 |> json(activity.data)
396 |> put_status(:bad_request)
401 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
403 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
405 as_nickname: user.nickname
409 |> put_status(:forbidden)
413 def errors(conn, {:error, :not_found}) do
415 |> put_status(:not_found)
416 |> json(dgettext("errors", "Not found"))
419 def errors(conn, _e) do
421 |> put_status(:internal_server_error)
422 |> json(dgettext("errors", "error"))
425 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
426 with actor <- conn.params["actor"],
427 true <- is_binary(actor) do
428 Pleroma.Instances.set_reachable(actor)
434 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
435 {:ok, new_user} = User.ensure_keys_present(user)
438 if new_user != user and match?(%User{}, for_user) do
439 User.get_cached_by_nickname(for_user.nickname)