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])
29 Pleroma.Plugs.OAuthScopesPlug,
30 %{scopes: ["read:accounts"]} when action in [:followers, :following]
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 |> set_cache_ttl_for(object)
64 |> put_resp_content_type("application/activity+json")
65 |> put_view(ObjectView)
66 |> render("object.json", object: object)
73 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
74 with ap_id <- o_status_url(conn, :object, uuid),
75 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
76 {_, true} <- {:public?, Visibility.is_public?(object)},
77 likes <- Utils.get_object_likes(object) do
78 {page, _} = Integer.parse(page)
81 |> put_resp_content_type("application/activity+json")
82 |> json(ObjectView.render("likes.json", ap_id, likes, page))
89 def object_likes(conn, %{"uuid" => uuid}) do
90 with ap_id <- o_status_url(conn, :object, uuid),
91 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
92 {_, true} <- {:public?, Visibility.is_public?(object)},
93 likes <- Utils.get_object_likes(object) do
95 |> put_resp_content_type("application/activity+json")
96 |> json(ObjectView.render("likes.json", ap_id, likes))
103 def activity(conn, %{"uuid" => uuid}) do
104 with ap_id <- o_status_url(conn, :activity, uuid),
105 %Activity{} = activity <- Activity.normalize(ap_id),
106 {_, true} <- {:public?, Visibility.is_public?(activity)} do
108 |> set_cache_ttl_for(activity)
109 |> put_resp_content_type("application/activity+json")
110 |> put_view(ObjectView)
111 |> render("object.json", object: activity)
113 {:public?, false} -> {:error, :not_found}
114 nil -> {:error, :not_found}
118 defp set_cache_ttl_for(conn, %Activity{object: object}) do
119 set_cache_ttl_for(conn, object)
122 defp set_cache_ttl_for(conn, entity) do
125 %Object{data: %{"type" => "Question"}} ->
126 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
129 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
135 assign(conn, :cache_ttl, ttl)
138 # GET /relay/following
139 def following(%{assigns: %{relay: true}} = conn, _params) do
141 |> put_resp_content_type("application/activity+json")
142 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
145 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
146 with %User{} = user <- User.get_cached_by_nickname(nickname),
147 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
148 {:show_follows, true} <-
149 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
150 {page, _} = Integer.parse(page)
153 |> put_resp_content_type("application/activity+json")
154 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
156 {:show_follows, _} ->
158 |> put_resp_content_type("application/activity+json")
159 |> send_resp(403, "")
163 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) 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) do
167 |> put_resp_content_type("application/activity+json")
168 |> json(UserView.render("following.json", %{user: user, for: for_user}))
172 # GET /relay/followers
173 def followers(%{assigns: %{relay: true}} = conn, _params) do
175 |> put_resp_content_type("application/activity+json")
176 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
179 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
180 with %User{} = user <- User.get_cached_by_nickname(nickname),
181 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
182 {:show_followers, true} <-
183 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
184 {page, _} = Integer.parse(page)
187 |> put_resp_content_type("application/activity+json")
188 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
190 {:show_followers, _} ->
192 |> put_resp_content_type("application/activity+json")
193 |> send_resp(403, "")
197 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) 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) do
201 |> put_resp_content_type("application/activity+json")
202 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
206 def outbox(conn, %{"nickname" => nickname} = params) do
207 with %User{} = user <- User.get_cached_by_nickname(nickname),
208 {:ok, user} <- User.ensure_keys_present(user) do
210 |> put_resp_content_type("application/activity+json")
211 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
215 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
216 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
217 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
218 true <- Utils.recipient_in_message(recipient, actor, params),
219 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
220 Federator.incoming_ap_doc(params)
225 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
226 Federator.incoming_ap_doc(params)
230 # only accept relayed Creates
231 def inbox(conn, %{"type" => "Create"} = params) do
233 "Signature missing or not from author, relayed Create message, fetching object from source"
236 Fetcher.fetch_object_from_id(params["object"]["id"])
241 def inbox(conn, params) do
242 headers = Enum.into(conn.req_headers, %{})
244 if String.contains?(headers["signature"], params["actor"]) do
246 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
249 Logger.info(inspect(conn.req_headers))
252 json(conn, dgettext("errors", "error"))
255 defp represent_service_actor(%User{} = user, conn) do
256 with {:ok, user} <- User.ensure_keys_present(user) do
258 |> put_resp_content_type("application/activity+json")
259 |> json(UserView.render("user.json", %{user: user}))
261 nil -> {:error, :not_found}
265 defp represent_service_actor(nil, _), do: {:error, :not_found}
267 def relay(conn, _params) do
269 |> represent_service_actor(conn)
272 def internal_fetch(conn, _params) do
273 InternalFetchActor.get_actor()
274 |> represent_service_actor(conn)
277 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
279 |> put_resp_content_type("application/activity+json")
280 |> json(UserView.render("user.json", %{user: user}))
283 def whoami(_conn, _params), do: {:error, :not_found}
286 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
287 %{"nickname" => nickname} = params
290 |> put_resp_content_type("application/activity+json")
291 |> put_view(UserView)
292 |> render("inbox.json", user: user, max_id: params["max_id"])
295 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
296 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
299 |> put_status(:forbidden)
303 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
304 "nickname" => nickname
307 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
309 as_nickname: as_nickname
313 |> put_status(:forbidden)
317 def handle_user_activity(user, %{"type" => "Create"} = params) do
320 |> Map.merge(Map.take(params, ["to", "cc"]))
321 |> Map.put("attributedTo", user.ap_id())
322 |> Transmogrifier.fix_object()
324 ActivityPub.create(%{
327 context: object["context"],
329 additional: Map.take(params, ["cc"])
333 def handle_user_activity(user, %{"type" => "Delete"} = params) do
334 with %Object{} = object <- Object.normalize(params["object"]),
335 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
336 {:ok, delete} <- ActivityPub.delete(object) do
339 _ -> {:error, dgettext("errors", "Can't delete object")}
343 def handle_user_activity(user, %{"type" => "Like"} = params) do
344 with %Object{} = object <- Object.normalize(params["object"]),
345 {:ok, activity, _object} <- ActivityPub.like(user, object) do
348 _ -> {:error, dgettext("errors", "Can't like object")}
352 def handle_user_activity(_, _) do
353 {:error, dgettext("errors", "Unhandled activity type")}
357 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
358 %{"nickname" => nickname} = params
365 |> Map.put("actor", actor)
366 |> Transmogrifier.fix_addressing()
368 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
370 |> put_status(:created)
371 |> put_resp_header("location", activity.data["id"])
372 |> json(activity.data)
376 |> put_status(:bad_request)
381 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
383 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
385 as_nickname: user.nickname
389 |> put_status(:forbidden)
393 def errors(conn, {:error, :not_found}) do
395 |> put_status(:not_found)
396 |> json(dgettext("errors", "Not found"))
399 def errors(conn, _e) do
401 |> put_status(:internal_server_error)
402 |> json(dgettext("errors", "error"))
405 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
406 with actor <- conn.params["actor"],
407 true <- is_binary(actor) do
408 Pleroma.Instances.set_reachable(actor)
414 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
415 {:ok, new_user} = User.ensure_keys_present(user)
418 if new_user != user and match?(%User{}, for_user) do
419 User.get_cached_by_nickname(for_user.nickname)