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} = params) 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 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
209 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
210 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
211 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
212 true <- Utils.recipient_in_message(recipient, actor, params),
213 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
214 Federator.incoming_ap_doc(params)
219 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
220 Federator.incoming_ap_doc(params)
224 # only accept relayed Creates
225 def inbox(conn, %{"type" => "Create"} = params) do
227 "Signature missing or not from author, relayed Create message, fetching object from source"
230 Fetcher.fetch_object_from_id(params["object"]["id"])
235 def inbox(conn, params) do
236 headers = Enum.into(conn.req_headers, %{})
238 if String.contains?(headers["signature"], params["actor"]) do
240 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
243 Logger.info(inspect(conn.req_headers))
246 json(conn, dgettext("errors", "error"))
249 defp represent_service_actor(%User{} = user, conn) do
250 with {:ok, user} <- User.ensure_keys_present(user) do
252 |> put_resp_content_type("application/activity+json")
253 |> json(UserView.render("user.json", %{user: user}))
255 nil -> {:error, :not_found}
259 defp represent_service_actor(nil, _), do: {:error, :not_found}
261 def relay(conn, _params) do
263 |> represent_service_actor(conn)
266 def internal_fetch(conn, _params) do
267 InternalFetchActor.get_actor()
268 |> represent_service_actor(conn)
271 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
273 |> put_resp_content_type("application/activity+json")
274 |> json(UserView.render("user.json", %{user: user}))
277 def whoami(_conn, _params), do: {:error, :not_found}
280 %{assigns: %{user: %{nickname: nickname} = user}} = conn,
281 %{"nickname" => nickname} = params
284 |> put_resp_content_type("application/activity+json")
285 |> put_view(UserView)
286 |> render("inbox.json", user: user, max_id: params["max_id"])
289 def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do
290 err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname)
293 |> put_status(:forbidden)
297 def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{
298 "nickname" => nickname
301 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
303 as_nickname: as_nickname
307 |> put_status(:forbidden)
311 def handle_user_activity(user, %{"type" => "Create"} = params) do
314 |> Map.merge(Map.take(params, ["to", "cc"]))
315 |> Map.put("attributedTo", user.ap_id())
316 |> Transmogrifier.fix_object()
318 ActivityPub.create(%{
321 context: object["context"],
323 additional: Map.take(params, ["cc"])
327 def handle_user_activity(user, %{"type" => "Delete"} = params) do
328 with %Object{} = object <- Object.normalize(params["object"]),
329 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
330 {:ok, delete} <- ActivityPub.delete(object) do
333 _ -> {:error, dgettext("errors", "Can't delete object")}
337 def handle_user_activity(user, %{"type" => "Like"} = params) do
338 with %Object{} = object <- Object.normalize(params["object"]),
339 {:ok, activity, _object} <- ActivityPub.like(user, object) do
342 _ -> {:error, dgettext("errors", "Can't like object")}
346 def handle_user_activity(_, _) do
347 {:error, dgettext("errors", "Unhandled activity type")}
351 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
352 %{"nickname" => nickname} = params
359 |> Map.put("actor", actor)
360 |> Transmogrifier.fix_addressing()
362 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
364 |> put_status(:created)
365 |> put_resp_header("location", activity.data["id"])
366 |> json(activity.data)
370 |> put_status(:bad_request)
375 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
377 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
379 as_nickname: user.nickname
383 |> put_status(:forbidden)
387 def errors(conn, {:error, :not_found}) do
389 |> put_status(:not_found)
390 |> json(dgettext("errors", "Not found"))
393 def errors(conn, _e) do
395 |> put_status(:internal_server_error)
396 |> json(dgettext("errors", "error"))
399 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
400 with actor <- conn.params["actor"],
401 true <- is_binary(actor) do
402 Pleroma.Instances.set_reachable(actor)
408 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
409 {:ok, new_user} = User.ensure_keys_present(user)
412 if new_user != user and match?(%User{}, for_user) do
413 User.get_cached_by_nickname(for_user.nickname)