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]
34 Pleroma.Plugs.OAuthScopesPlug,
35 %{scopes: ["read:accounts"]} when action in [:followers, :following]
38 plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])
39 plug(:set_requester_reachable when action in [:inbox])
40 plug(:relay_active? when action in [:relay])
42 def relay_active?(conn, _) do
43 if Pleroma.Config.get([:instance, :allow_relay]) do
47 |> render_error(:not_found, "not found")
52 def user(conn, %{"nickname" => nickname}) do
53 with %User{} = user <- User.get_cached_by_nickname(nickname),
54 {:ok, user} <- User.ensure_keys_present(user) do
56 |> put_resp_content_type("application/activity+json")
57 |> json(UserView.render("user.json", %{user: user}))
59 nil -> {:error, :not_found}
63 def object(conn, %{"uuid" => uuid}) do
64 with ap_id <- o_status_url(conn, :object, uuid),
65 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
66 {_, true} <- {:public?, Visibility.is_public?(object)} do
68 |> assign(:tracking_fun_data, object.id)
69 |> set_cache_ttl_for(object)
70 |> put_resp_content_type("application/activity+json")
71 |> put_view(ObjectView)
72 |> render("object.json", object: object)
79 def track_object_fetch(conn, nil), do: conn
81 def track_object_fetch(conn, object_id) do
82 with %{assigns: %{user: %User{id: user_id}}} <- conn do
83 Delivery.create(object_id, user_id)
89 def object_likes(conn, %{"uuid" => uuid, "page" => page}) 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
94 {page, _} = Integer.parse(page)
97 |> put_resp_content_type("application/activity+json")
98 |> json(ObjectView.render("likes.json", ap_id, likes, page))
105 def object_likes(conn, %{"uuid" => uuid}) do
106 with ap_id <- o_status_url(conn, :object, uuid),
107 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
108 {_, true} <- {:public?, Visibility.is_public?(object)},
109 likes <- Utils.get_object_likes(object) do
111 |> put_resp_content_type("application/activity+json")
112 |> json(ObjectView.render("likes.json", ap_id, likes))
119 def activity(conn, %{"uuid" => uuid}) do
120 with ap_id <- o_status_url(conn, :activity, uuid),
121 %Activity{} = activity <- Activity.normalize(ap_id),
122 {_, true} <- {:public?, Visibility.is_public?(activity)} do
124 |> maybe_set_tracking_data(activity)
125 |> set_cache_ttl_for(activity)
126 |> put_resp_content_type("application/activity+json")
127 |> put_view(ObjectView)
128 |> render("object.json", object: activity)
130 {:public?, false} -> {:error, :not_found}
131 nil -> {:error, :not_found}
135 defp maybe_set_tracking_data(conn, %Activity{data: %{"type" => "Create"}} = activity) do
136 object_id = Object.normalize(activity).id
137 assign(conn, :tracking_fun_data, object_id)
140 defp maybe_set_tracking_data(conn, _activity), do: conn
142 defp set_cache_ttl_for(conn, %Activity{object: object}) do
143 set_cache_ttl_for(conn, object)
146 defp set_cache_ttl_for(conn, entity) do
149 %Object{data: %{"type" => "Question"}} ->
150 Pleroma.Config.get([:web_cache_ttl, :activity_pub_question])
153 Pleroma.Config.get([:web_cache_ttl, :activity_pub])
159 assign(conn, :cache_ttl, ttl)
162 # GET /relay/following
163 def following(%{assigns: %{relay: true}} = conn, _params) do
165 |> put_resp_content_type("application/activity+json")
166 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
169 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
170 with %User{} = user <- User.get_cached_by_nickname(nickname),
171 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
172 {:show_follows, true} <-
173 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
174 {page, _} = Integer.parse(page)
177 |> put_resp_content_type("application/activity+json")
178 |> json(UserView.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 |> json(UserView.render("following.json", %{user: user, for: for_user}))
196 # GET /relay/followers
197 def followers(%{assigns: %{relay: true}} = conn, _params) do
199 |> put_resp_content_type("application/activity+json")
200 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
203 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
204 with %User{} = user <- User.get_cached_by_nickname(nickname),
205 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
206 {:show_followers, true} <-
207 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
208 {page, _} = Integer.parse(page)
211 |> put_resp_content_type("application/activity+json")
212 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
214 {:show_followers, _} ->
216 |> put_resp_content_type("application/activity+json")
217 |> send_resp(403, "")
221 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
222 with %User{} = user <- User.get_cached_by_nickname(nickname),
223 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
225 |> put_resp_content_type("application/activity+json")
226 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
230 def outbox(conn, %{"nickname" => nickname} = params) 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 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
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} = params
314 |> put_resp_content_type("application/activity+json")
315 |> put_view(UserView)
316 |> render("inbox.json", user: user, max_id: params["max_id"])
319 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
320 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
323 |> put_status(:forbidden)
327 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
328 "nickname" => nickname
331 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
333 as_nickname: as_nickname
337 |> put_status(:forbidden)
341 def handle_user_activity(user, %{"type" => "Create"} = params) do
344 |> Map.merge(Map.take(params, ["to", "cc"]))
345 |> Map.put("attributedTo", user.ap_id())
346 |> Transmogrifier.fix_object()
348 ActivityPub.create(%{
351 context: object["context"],
353 additional: Map.take(params, ["cc"])
357 def handle_user_activity(user, %{"type" => "Delete"} = params) do
358 with %Object{} = object <- Object.normalize(params["object"]),
359 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
360 {:ok, delete} <- ActivityPub.delete(object) do
363 _ -> {:error, dgettext("errors", "Can't delete object")}
367 def handle_user_activity(user, %{"type" => "Like"} = params) do
368 with %Object{} = object <- Object.normalize(params["object"]),
369 {:ok, activity, _object} <- ActivityPub.like(user, object) do
372 _ -> {:error, dgettext("errors", "Can't like object")}
376 def handle_user_activity(_, _) do
377 {:error, dgettext("errors", "Unhandled activity type")}
381 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
382 %{"nickname" => nickname} = params
389 |> Map.put("actor", actor)
390 |> Transmogrifier.fix_addressing()
392 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
394 |> put_status(:created)
395 |> put_resp_header("location", activity.data["id"])
396 |> json(activity.data)
400 |> put_status(:bad_request)
405 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
407 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
409 as_nickname: user.nickname
413 |> put_status(:forbidden)
417 def errors(conn, {:error, :not_found}) do
419 |> put_status(:not_found)
420 |> json(dgettext("errors", "Not found"))
423 def errors(conn, _e) do
425 |> put_status(:internal_server_error)
426 |> json(dgettext("errors", "error"))
429 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
430 with actor <- conn.params["actor"],
431 true <- is_binary(actor) do
432 Pleroma.Instances.set_reachable(actor)
438 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
439 {:ok, new_user} = User.ensure_keys_present(user)
442 if new_user != user and match?(%User{}, for_user) do
443 User.get_cached_by_nickname(for_user.nickname)