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.Web.FederatingPlug when action in [:inbox, :relay])
27 plug(:set_requester_reachable when action in [:inbox])
28 plug(:relay_active? when action in [:relay])
30 def relay_active?(conn, _) do
31 if Pleroma.Config.get([:instance, :allow_relay]) do
35 |> render_error(:not_found, "not found")
40 def user(conn, %{"nickname" => nickname}) do
41 with %User{} = user <- User.get_cached_by_nickname(nickname),
42 {:ok, user} <- User.ensure_keys_present(user) do
44 |> put_resp_content_type("application/activity+json")
45 |> json(UserView.render("user.json", %{user: user}))
47 nil -> {:error, :not_found}
51 def object(conn, %{"uuid" => uuid}) do
52 with ap_id <- o_status_url(conn, :object, uuid),
53 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
54 {_, true} <- {:public?, Visibility.is_public?(object)} do
56 |> put_resp_content_type("application/activity+json")
57 |> json(ObjectView.render("object.json", %{object: object}))
64 def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
65 with ap_id <- o_status_url(conn, :object, uuid),
66 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
67 {_, true} <- {:public?, Visibility.is_public?(object)},
68 likes <- Utils.get_object_likes(object) do
69 {page, _} = Integer.parse(page)
72 |> put_resp_content_type("application/activity+json")
73 |> json(ObjectView.render("likes.json", ap_id, likes, page))
80 def object_likes(conn, %{"uuid" => uuid}) do
81 with ap_id <- o_status_url(conn, :object, uuid),
82 %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
83 {_, true} <- {:public?, Visibility.is_public?(object)},
84 likes <- Utils.get_object_likes(object) do
86 |> put_resp_content_type("application/activity+json")
87 |> json(ObjectView.render("likes.json", ap_id, likes))
94 def activity(conn, %{"uuid" => uuid}) do
95 with ap_id <- o_status_url(conn, :activity, uuid),
96 %Activity{} = activity <- Activity.normalize(ap_id),
97 {_, true} <- {:public?, Visibility.is_public?(activity)} do
99 |> put_resp_content_type("application/activity+json")
100 |> json(ObjectView.render("object.json", %{object: activity}))
107 # GET /relay/following
108 def following(%{assigns: %{relay: true}} = conn, _params) do
110 |> put_resp_content_type("application/activity+json")
111 |> json(UserView.render("following.json", %{user: Relay.get_actor()}))
114 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
115 with %User{} = user <- User.get_cached_by_nickname(nickname),
116 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
117 {:show_follows, true} <-
118 {:show_follows, (for_user && for_user == user) || !user.info.hide_follows} do
119 {page, _} = Integer.parse(page)
122 |> put_resp_content_type("application/activity+json")
123 |> json(UserView.render("following.json", %{user: user, page: page, for: for_user}))
125 {:show_follows, _} ->
127 |> put_resp_content_type("application/activity+json")
128 |> send_resp(403, "")
132 def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
133 with %User{} = user <- User.get_cached_by_nickname(nickname),
134 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
136 |> put_resp_content_type("application/activity+json")
137 |> json(UserView.render("following.json", %{user: user, for: for_user}))
141 # GET /relay/followers
142 def followers(%{assigns: %{relay: true}} = conn, _params) do
144 |> put_resp_content_type("application/activity+json")
145 |> json(UserView.render("followers.json", %{user: Relay.get_actor()}))
148 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do
149 with %User{} = user <- User.get_cached_by_nickname(nickname),
150 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user),
151 {:show_followers, true} <-
152 {:show_followers, (for_user && for_user == user) || !user.info.hide_followers} do
153 {page, _} = Integer.parse(page)
156 |> put_resp_content_type("application/activity+json")
157 |> json(UserView.render("followers.json", %{user: user, page: page, for: for_user}))
159 {:show_followers, _} ->
161 |> put_resp_content_type("application/activity+json")
162 |> send_resp(403, "")
166 def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do
167 with %User{} = user <- User.get_cached_by_nickname(nickname),
168 {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
170 |> put_resp_content_type("application/activity+json")
171 |> json(UserView.render("followers.json", %{user: user, for: for_user}))
175 def outbox(conn, %{"nickname" => nickname} = params) do
176 with %User{} = user <- User.get_cached_by_nickname(nickname),
177 {:ok, user} <- User.ensure_keys_present(user) do
179 |> put_resp_content_type("application/activity+json")
180 |> json(UserView.render("outbox.json", %{user: user, max_id: params["max_id"]}))
184 def inbox(%{assigns: %{valid_signature: true}} = conn, %{"nickname" => nickname} = params) do
185 with %User{} = recipient <- User.get_cached_by_nickname(nickname),
186 {:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(params["actor"]),
187 true <- Utils.recipient_in_message(recipient, actor, params),
188 params <- Utils.maybe_splice_recipient(recipient.ap_id, params) do
189 Federator.incoming_ap_doc(params)
194 def inbox(%{assigns: %{valid_signature: true}} = conn, params) do
195 Federator.incoming_ap_doc(params)
199 # only accept relayed Creates
200 def inbox(conn, %{"type" => "Create"} = params) do
202 "Signature missing or not from author, relayed Create message, fetching object from source"
205 Fetcher.fetch_object_from_id(params["object"]["id"])
210 def inbox(conn, params) do
211 headers = Enum.into(conn.req_headers, %{})
213 if String.contains?(headers["signature"], params["actor"]) do
215 "Signature validation error for: #{params["actor"]}, make sure you are forwarding the HTTP Host header!"
218 Logger.info(inspect(conn.req_headers))
221 json(conn, dgettext("errors", "error"))
224 defp represent_service_actor(%User{} = user, conn) do
225 with {:ok, user} <- User.ensure_keys_present(user) do
227 |> put_resp_content_type("application/activity+json")
228 |> json(UserView.render("user.json", %{user: user}))
230 nil -> {:error, :not_found}
234 defp represent_service_actor(nil, _), do: {:error, :not_found}
236 def relay(conn, _params) do
238 |> represent_service_actor(conn)
241 def internal_fetch(conn, _params) do
242 InternalFetchActor.get_actor()
243 |> represent_service_actor(conn)
246 def whoami(%{assigns: %{user: %User{} = user}} = conn, _params) do
248 |> put_resp_content_type("application/activity+json")
249 |> json(UserView.render("user.json", %{user: user}))
252 def whoami(_conn, _params), do: {:error, :not_found}
254 def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do
255 if nickname == user.nickname do
257 |> put_resp_content_type("application/activity+json")
258 |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]}))
261 dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}",
263 as_nickname: user.nickname
267 |> put_status(:forbidden)
272 def handle_user_activity(user, %{"type" => "Create"} = params) do
275 |> Map.merge(Map.take(params, ["to", "cc"]))
276 |> Map.put("attributedTo", user.ap_id())
277 |> Transmogrifier.fix_object()
279 ActivityPub.create(%{
282 context: object["context"],
284 additional: Map.take(params, ["cc"])
288 def handle_user_activity(user, %{"type" => "Delete"} = params) do
289 with %Object{} = object <- Object.normalize(params["object"]),
290 true <- user.info.is_moderator || user.ap_id == object.data["actor"],
291 {:ok, delete} <- ActivityPub.delete(object) do
294 _ -> {:error, dgettext("errors", "Can't delete object")}
298 def handle_user_activity(user, %{"type" => "Like"} = params) do
299 with %Object{} = object <- Object.normalize(params["object"]),
300 {:ok, activity, _object} <- ActivityPub.like(user, object) do
303 _ -> {:error, dgettext("errors", "Can't like object")}
307 def handle_user_activity(_, _) do
308 {:error, dgettext("errors", "Unhandled activity type")}
312 %{assigns: %{user: %User{nickname: nickname} = user}} = conn,
313 %{"nickname" => nickname} = params
320 |> Map.put("actor", actor)
321 |> Transmogrifier.fix_addressing()
323 with {:ok, %Activity{} = activity} <- handle_user_activity(user, params) do
325 |> put_status(:created)
326 |> put_resp_header("location", activity.data["id"])
327 |> json(activity.data)
331 |> put_status(:bad_request)
336 def update_outbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = _) do
338 dgettext("errors", "can't update outbox of %{nickname} as %{as_nickname}",
340 as_nickname: user.nickname
344 |> put_status(:forbidden)
348 def errors(conn, {:error, :not_found}) do
350 |> put_status(:not_found)
351 |> json(dgettext("errors", "Not found"))
354 def errors(conn, _e) do
356 |> put_status(:internal_server_error)
357 |> json(dgettext("errors", "error"))
360 defp set_requester_reachable(%Plug.Conn{} = conn, _) do
361 with actor <- conn.params["actor"],
362 true <- is_binary(actor) do
363 Pleroma.Instances.set_reachable(actor)
369 defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do
370 {:ok, new_user} = User.ensure_keys_present(user)
373 if new_user != user and match?(%User{}, for_user) do
374 User.get_cached_by_nickname(for_user.nickname)